栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

Java for循环优化

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java for循环优化

容易被手工制作的微基准所迷惑-您永远不知道它们的 实际
测量值。这就是为什么有诸如JMH之类的特殊工具的原因。但是,让我们分析一下原始的手工基准会发生什么:

static class HDouble {    double value;}public static void main(String[] args) {    primitive();    wrapper();}public static void primitive() {    long start = System.nanoTime();    for (double d = 0; d < 1000000000; d++) {    }    long end = System.nanoTime();    System.out.printf("Primitive: %.3f sn", (end - start) / 1e9);}public static void wrapper() {    HDouble d = new HDouble();    long start = System.nanoTime();    for (d.value = 0; d.value < 1000000000; d.value++) {    }    long end = System.nanoTime();    System.out.printf("Wrapper:   %.3f sn", (end - start) / 1e9);}

结果有点类似于您的结果:

Primitive: 3.618 sWrapper:   1.380 s

现在重复测试几次:

public static void main(String[] args) {    for (int i = 0; i < 5; i++) {        primitive();        wrapper();    }}

它变得更加有趣:

Primitive: 3.661 sWrapper:   1.382 sPrimitive: 3.461 sWrapper:   1.380 sPrimitive: 1.376 s <-- starting from 3rd iterationWrapper:   1.381 s <-- the timings become equalPrimitive: 1.371 sWrapper:   1.372 sPrimitive: 1.379 sWrapper:   1.378 s

看来这两种方法都最终得到了优化。再次运行它,现在记录JIT编译器活动:

-XX:-TieredCompilation-XX:Compileonly=Test -XX:+PrintCompilation

    136    1 %Test::primitive @ 6 (53 bytes)   3725    1 %Test::primitive @ -2 (53 bytes)   made not entrantPrimitive: 3.589 s   3748    2 %Test::wrapper @ 17 (73 bytes)   5122    2 %Test::wrapper @ -2 (73 bytes)   made not entrantWrapper:   1.374 s   5122    3  Test::primitive (53 bytes)   5124    4 %Test::primitive @ 6 (53 bytes)Primitive: 3.421 s   8544    5  Test::wrapper (73 bytes)   8547    6 %Test::wrapper @ 17 (73 bytes)Wrapper:   1.378 sPrimitive: 1.372 sWrapper:   1.375 sPrimitive: 1.378 sWrapper:   1.373 sPrimitive: 1.375 sWrapper:   1.378 s

注意

%
在第一次迭代时登录编译日志。这意味着这些方法是在OSR
(堆栈替换)模式下编译的。在第二次迭代中,方法以正常模式重新编译。从那时起,从第三次迭代开始,原语和包装器之间的执行速度就没有区别。

您实际测量的是OSR存根的性能。它通常与应用程序的实际性能无关,因此您不必在意它。

但是问题仍然存在,为什么包装的OSR存根比原始变量的编译好?为了找出答案,我们需要深入研究生成的汇编代码:

-XX:Compileonly=Test -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

我将忽略所有不相关的代码,仅保留编译循环。

原始:

0x00000000023e90d0: vmovsd 0x28(%rsp),%xmm1      <-- load double from the stack0x00000000023e90d6: vaddsd -0x7e(%rip),%xmm1,%xmm10x00000000023e90de: test   %eax,-0x21f90e4(%rip)0x00000000023e90e4: vmovsd %xmm1,0x28(%rsp)      <-- store to the stack0x00000000023e90ea: vucomisd 0x28(%rsp),%xmm0    <-- compare with the stack value0x00000000023e90f0: ja     0x00000000023e90d0

包装器:

0x00000000023ebe90: vaddsd -0x78(%rip),%xmm0,%xmm00x00000000023ebe98: vmovsd %xmm0,0x10(%rbx)      <-- store to the object field0x00000000023ebe9d: test   %eax,-0x21fbea3(%rip)0x00000000023ebea3: vucomisd %xmm0,%xmm1         <-- compare registers0x00000000023ebea7: ja     0x00000000023ebe90

如您所见,“原始”情况会产生许多负载并存储到堆栈位置,而“包装器”主要执行寄存器内操作。为什么OSR存根引用堆栈是完全可以理解的:在解释模式下,局部变量存储在堆栈中,并使OSR存根与此解释帧兼容。在“包装器”情况下,该值存储在堆中,并且对该对象的引用已缓存在寄存器中。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/496342.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号