【问题标题】:Performance of new operator versus newInstance() in JavaJava 中 new 运算符与 newInstance() 的性能
【发布时间】:2010-07-21 00:23:42
【问题描述】:

我在代码的某种性能关键区域中使用了newInstance()。 方法签名是:

<T extends SomethingElse> T create(Class<T> clasz)

我将Something.class 作为参数传递,我得到一个SomethingElse 的实例,它是用newInstance() 创建的。

今天我回来从列表中清除了这个性能 TODO,所以我对new 运算符与newInstance() 进行了几次测试。我对newInstance() 的性能损失感到非常惊讶。

我在这里写了一点:http://biasedbit.com/blog/new-vs-newinstance/

(对不起,自我推销......我会把文字放在这里,但这个问题会变得不成比例。)

我想知道的是,为什么 -server 标志会在创建的对象数量大幅增长而不是“低”值(例如 100 或 1000)时提供如此高的性能提升。

我确实从整个反射的事情中吸取了教训,这只是对 JVM 在运行时执行的优化的好奇,尤其是使用 -server 标志。另外,如果我在测试中做错了什么,我会感谢您的反馈!


编辑:我添加了一个预热阶段,结果现在更加稳定。感谢您的意见!

【问题讨论】:

标签: java performance reflection instantiation


【解决方案1】:

我确实从整个反射中吸取了教训,这只是对 JVM 在运行时执行的优化感到好奇,尤其是使用 -server 标志。另外,如果我在测试中做错了什么,我会感谢您的反馈!

首先回答第二部分,您的代码似乎犯了 Java 微基准测试的经典错误,并且在进行测量之前没有“预热”JVM。您的应用程序需要运行几次执行测试的方法,忽略最初的几次迭代……至少在数字稳定之前。原因是 JVM 必须做很多工作才能启动应用程序。例如加载类并(当它们运行几次时)JIT 编译花费大量应用程序时间的方法。

我认为“-server”之所以有所作为,是因为(除其他外)它改变了决定何时进行 JIT 编译的规则。假设对于“服务器”来说,JIT 越早越好,这会使启动速度变慢,但吞吐量会更高。 (相比之下,“客户端”被调整为推迟 JIT 编译,以便用户更快地获得工作的 GUI。)

【讨论】:

  • 在运行实际测试之前,我已经添加了几次热身迭代,我得到了更好、更稳定的数据。使用 -server 标志,newInstance() 稳定在比 new 慢 2.5~2.9 倍(对于 100 和 1kk 对象)。如果没有 -server,对于 100 个对象,它仍然会慢 8~9 倍,对于 1kk,它会慢 25 倍)。很好的建议! :)
  • 正确预热 JVM 的更好方法是向它传递 -XX:+PrintCompilation 命令行参数,这将使 JVM 在执行时打印出所有 JIT 编译和优化.然后确保在您的定时运行期间没有任何编译步骤发生。
  • @LordOfThePigs - 我不同意。一般来说,最好的方法是运行大量(交替)迭代,记录时间,并丢弃早期的异常。然后平均。这会处理其他问题来源以及 JITing。 -XX:+PrintCompilation 方法仅解决 JIT 异常。
【解决方案2】:

恕我直言,性能损失来自类加载机制。 在反射的情况下,所有安全机制都被使用,因此创建惩罚更高。 对于 new 运算符,类已经加载到 VM 中(由默认类加载器检查和准备),实际实例化是一个廉价的过程。 -server 参数对常用代码进行了大量的 JIT 优化。您可能还想尝试使用 -batch 参数来权衡启动时间,但代码会运行得更快。

【讨论】:

【解决方案3】:

除其他外,-server 选项的垃圾收集配置文件与 survivor space sizing 默认值有很大不同。

仔细阅读后,我发现您的示例是micro-benchmark,结果可能与直觉相反。例如,在我的平台上,对newInstance() 的重复调用在重复运行期间被有效优化掉,使newInstance() 看起来比new 快12.5 倍。

【讨论】:

  • 我使用 -XX:+PrintGCDetails 运行,但我只看到了 1 个集合——我在两个测试之间强制执行的完整集合。我确保为应用程序提供足够的内存,以便它不需要调整大小或收集(实际上我也通过使用与 -Xms 相同的 -Xmx 来防止调整大小)。在这些条件下,我相信 GC 并不是结果的决定性因素。
  • @brunodecarvalho:是的,我明白你的意思,我已经在上面详细说明了。您可能需要重写基准以获得有意义的结果。
  • 是的,我害怕这里的微基准效应。我只是将新实例添加到该对象数组中,希望编译器不会意识到这是一个毫无意义的操作并完全删除整个循环。我将使用我将要优化的系统再次对此进行测试。但是,我发现 newInstance() 比 new 更快有点奇怪。查看 JDK 的源代码,似乎 newInstance() 经历了很多开销,而 new 没有。
  • @brunodecarvalho:我认为一个平凡的构造函数会被优化掉,而一个非平凡的构造函数会主导两者的时间。
猜你喜欢
  • 2013-06-05
  • 2016-04-12
  • 1970-01-01
  • 2011-05-24
  • 2010-10-30
  • 1970-01-01
  • 1970-01-01
  • 2015-04-19
相关资源
最近更新 更多