Java中new操作符与newInstance()的性能比较

11
我在代码的性能关键区域中使用了newInstance()方法。该方法的签名为: <T extends SomethingElse> T create(Class<T> clasz) 我传递Something.class作为参数,然后使用newInstance()创建SomethingElse的实例。
今天我回到这个性能待办事项清单上,对比了一下new运算符和newInstance()的性能。我对newInstance()的性能惩罚感到非常惊讶。
我写了一点关于它的内容,可以在这里查看:http://biasedbit.com/blog/new-vs-newinstance/ (抱歉,我自己做了一些推广...我会把文本放在这里,但是问题会变得过于庞大。)
我想知道的是,为什么当创建的对象数量大幅增长时,-server标志提供如此高的性能提升,而当值较低时,例如100或1000,则没有提升。
我确实从整个反射中学到了教训,这只是对JVM在运行时执行的优化的好奇,特别是在-server标志的情况下。此外,如果我的测试有什么问题,请告诉我!

这篇博客文章又 迁移了 - Tom Anderson
该文章 http://biasedbit.com/new-vs-newinstance 不存在! - Vivek
3个回答

5
我已经从反射中吸取了教训,现在只是对JVM在运行时进行优化的好奇。特别是针对-server标志。另外,如果我的测试有误,请给我反馈! 首先回答第二部分,你的代码似乎犯了Java微基准测试的经典错误,即在进行测量之前没有"热身"JVM。你的应用程序需要运行执行测试的方法几次,忽略前几次迭代...至少要等到数字稳定下来。原因是JVM必须做很多工作才能启动应用程序,比如加载类和(当它们运行了几次后)JIT编译重要的应用程序时间所在的方法。 我认为"-server"产生影响的原因是,它改变了决定何时进行JIT编译的规则。假设对于"服务器"来说,更早地JIT编译会更好,这会导致启动速度较慢但吞吐量更高。(相比之下,“客户端”被调整为推迟JIT编译,以便用户更快地获得可工作的GUI。)

我在实际测试之前添加了几个热身迭代,现在得到了更好、更稳定的数据。使用-server标志,newInstance()方法相对于new方法稳定在2.52.9倍的速度下(无论是100个对象还是1kk个对象)。如果没有使用-server标志,则对于100个对象,速度仍然会慢89倍,对于1kk个对象则慢25倍左右。非常好的建议! :) - biasedbit
更好的预热JVM的方法是传递-XX:+PrintCompilation命令行参数,这将使JVM在执行时打印出所有JIT编译和优化。然后确保在定时运行期间没有发生任何编译步骤。 - LordOfThePigs
@LordOfThePigs - 我不同意。一般来说,最好的方法是运行大量(交替)迭代,记录时间,并且丢弃异常的早期迭代。然后取平均值。这样做可以解决其他问题源以及JIT编译的问题。"-XX:+PrintCompilation"方法只能解决JIT编译的异常情况。 - Stephen C

1

除其他事项外,-server选项的垃圾回收配置文件具有显着不同的幸存者空间大小默认值。

仔细阅读后,我发现您的例子是一个微型基准测试,结果可能是违反直觉的。例如,在我的平台上,对newInstance()的重复调用在重复运行期间被有效地优化掉,使得newInstance()看起来比new快12.5倍。


我使用了-XX:+PrintGCDetails运行,并且只看到了1个收集 - 在两个测试之间强制进行的完整收集。我确保为应用程序提供足够的内存,以便它不需要调整大小或收集(实际上,我还通过使用与-Xms相同的-Xmx来防止调整大小)。在这些条件下,我认为GC并不是结果的决定性因素。 - biasedbit
@brunodecarvalho:是的,我明白你的意思,并且我已经在上面进行了详细阐述。你可能需要重新编写你的基准测试以获得有意义的结果。 - trashgod
是的,我担心这里会出现微基准测试效应。我只是将新实例添加到该对象数组中,希望编译器不会意识到这是一个无意义的操作,并删除整个循环。我将使用我即将优化的系统再次测试这个。然而,我发现newInstance()比new更快有点奇怪。通过查看JDK的源代码,似乎newInstance()经历了很多开销,而new没有。 - biasedbit
@brunodecarvalho:我认为一个简单的构造函数会被优化掉,而一个非简单的构造函数会支配任何时间。 - trashgod

1

在我看来,性能损失来自类加载机制。 反射的情况下会使用所有的安全机制,因此创建的成本更高。 如果使用new运算符,则类已经在VM中加载(通过默认的类加载器进行检查和准备),实例化是一个便宜的过程。 -server参数对经常使用的代码进行了很多JIT优化。您可能还想尝试使用-batch参数,它会牺牲启动时间,但代码将运行得更快。


你的解释很有道理,因为我已经查看了代码并发现整个安全机制会增加很大的开销。然而,“-batch”标志似乎无效。 - biasedbit
@brunodecarvalho:你是对的!语法应该是-Xbatch而不是我打的batch。详情请参见:http://download.oracle.com/docs/cd/E17476_01/javase/1.4.2/docs/tooldocs/windows/java.html和http://download.oracle.com/docs/cd/E17409_01/javase/6/docs/technotes/tools/windows/java.html。 - Daniel Voina
似乎没有什么不同。我删除了预热阶段,以查看它是否会影响结果,但使用-Xbatch和不使用时得到的值几乎相同。不过,这是一个非常有趣的标志! - biasedbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接