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

为什么2 *(i * i)比Java中的2 * i * i快?

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

为什么2 *(i * i)比Java中的2 * i * i快?

字节码的顺序略有不同。

2 * (i * i)

     iconst_2     iload0     iload0     imul     imul     iadd

vs

2 * i * i

     iconst_2     iload0     imul     iload0     imul     iadd

乍看之下,这没有什么区别;如果有的话,第二个版本更理想,因为它减少了一个插槽。

因此,我们需要更深入地研究较低级别(JIT)1。

请记住,JIT倾向于非常积极地展开小循环。的确,我们发现该

2 * (i * i)
案例的展开速度是16倍:

030   B2: # B2 B3 <- B1 B2  Loop: B2-B2 inner main of N18 Freq: 1e+006030     addl    R11, RBP    # int033     movl    RBP, R13    # spill036     addl    RBP, #14    # int039     imull   RBP, RBP    # int03c     movl    R9, R13 # spill03f     addl    R9, #13 # int043     imull   R9, R9  # int047     sall    RBP, #1049     sall    R9, #104c     movl    R8, R13 # spill04f     addl    R8, #15 # int053     movl    R10, R8 # spill056     movdl   XMM1, R8    # spill05b     imull   R10, R8 # int05f     movl    R8, R13 # spill062     addl    R8, #12 # int066     imull   R8, R8  # int06a     sall    R10, #106d     movl    [rsp + #32], R10    # spill072     sall    R8, #1075     movl    RBX, R13    # spill078     addl    RBX, #11    # int07b     imull   RBX, RBX    # int07e     movl    RCX, R13    # spill081     addl    RCX, #10    # int084     imull   RCX, RCX    # int087     sall    RBX, #1089     sall    RCX, #108b     movl    RDX, R13    # spill08e     addl    RDX, #8 # int091     imull   RDX, RDX    # int094     movl    RDI, R13    # spill097     addl    RDI, #7 # int09a     imull   RDI, RDI    # int09d     sall    RDX, #109f     sall    RDI, #10a1     movl    RAX, R13    # spill0a4     addl    RAX, #6 # int0a7     imull   RAX, RAX    # int0aa     movl    RSI, R13    # spill0ad     addl    RSI, #4 # int0b0     imull   RSI, RSI    # int0b3     sall    RAX, #10b5     sall    RSI, #10b7     movl    R10, R13    # spill0ba     addl    R10, #2 # int0be     imull   R10, R10    # int0c2     movl    R14, R13    # spill0c5     incl    R14 # int0c8     imull   R14, R14    # int0cc     sall    R10, #10cf     sall    R14, #10d2     addl    R14, R11    # int0d5     addl    R14, R10    # int0d8     movl    R10, R13    # spill0db     addl    R10, #3 # int0df     imull   R10, R10    # int0e3     movl    R11, R13    # spill0e6     addl    R11, #5 # int0ea     imull   R11, R11    # int0ee     sall    R10, #10f1     addl    R10, R14    # int0f4     addl    R10, RSI    # int0f7     sall    R11, #10fa     addl    R11, R10    # int0fd     addl    R11, RAX    # int100     addl    R11, RDI    # int103     addl    R11, RDX    # int106     movl    R10, R13    # spill109     addl    R10, #9 # int10d     imull   R10, R10    # int111     sall    R10, #1114     addl    R10, R11    # int117     addl    R10, RCX    # int11a     addl    R10, RBX    # int11d     addl    R10, R8 # int120     addl    R9, R10 # int123     addl    RBP, R9 # int126     addl    RBP, [RSP + #32 (32-bit)]   # int12a     addl    R13, #16    # int12e     movl    R11, R13    # spill131     imull   R11, R13    # int135     sall    R11, #1138     cmpl    R13, #99999998513f     jl     B2   # loop end  P=1.000000 C=6554623.000000

我们看到有1个寄存器被“堆放”到堆栈中。

对于

2 * i * i
版本:

05a   B3: # B2 B4 <- B1 B2  Loop: B3-B2 inner main of N18 Freq: 1e+00605a     addl    RBX, R11    # int05d     movl    [rsp + #32], RBX    # spill061     movl    R11, R8 # spill064     addl    R11, #15    # int068     movl    [rsp + #36], R11    # spill06d     movl    R11, R8 # spill070     addl    R11, #14    # int074     movl    R10, R9 # spill077     addl    R10, #16    # int07b     movdl   XMM2, R10   # spill080     movl    RCX, R9 # spill083     addl    RCX, #14    # int086     movdl   XMM1, RCX   # spill08a     movl    R10, R9 # spill08d     addl    R10, #12    # int091     movdl   XMM4, R10   # spill096     movl    RCX, R9 # spill099     addl    RCX, #10    # int09c     movdl   XMM6, RCX   # spill0a0     movl    RBX, R9 # spill0a3     addl    RBX, #8 # int0a6     movl    RCX, R9 # spill0a9     addl    RCX, #6 # int0ac     movl    RDX, R9 # spill0af     addl    RDX, #4 # int0b2     addl    R9, #2  # int0b6     movl    R10, R14    # spill0b9     addl    R10, #22    # int0bd     movdl   XMM3, R10   # spill0c2     movl    RDI, R14    # spill0c5     addl    RDI, #20    # int0c8     movl    RAX, R14    # spill0cb     addl    RAX, #32    # int0ce     movl    RSI, R14    # spill0d1     addl    RSI, #18    # int0d4     movl    R13, R14    # spill0d7     addl    R13, #24    # int0db     movl    R10, R14    # spill0de     addl    R10, #26    # int0e2     movl    [rsp + #40], R10    # spill0e7     movl    RBP, R14    # spill0ea     addl    RBP, #28    # int0ed     imull   RBP, R11    # int0f1     addl    R14, #30    # int0f5     imull   R14, [RSP + #36 (32-bit)]   # int0fb     movl    R10, R8 # spill0fe     addl    R10, #11    # int102     movdl   R11, XMM3   # spill107     imull   R11, R10    # int10b     movl    [rsp + #44], R11    # spill110     movl    R10, R8 # spill113     addl    R10, #10    # int117     imull   RDI, R10    # int11b     movl    R11, R8 # spill11e     addl    R11, #8 # int122     movdl   R10, XMM2   # spill127     imull   R10, R11    # int12b     movl    [rsp + #48], R10    # spill130     movl    R10, R8 # spill133     addl    R10, #7 # int137     movdl   R11, XMM1   # spill13c     imull   R11, R10    # int140     movl    [rsp + #52], R11    # spill145     movl    R11, R8 # spill148     addl    R11, #6 # int14c     movdl   R10, XMM4   # spill151     imull   R10, R11    # int155     movl    [rsp + #56], R10    # spill15a     movl    R10, R8 # spill15d     addl    R10, #5 # int161     movdl   R11, XMM6   # spill166     imull   R11, R10    # int16a     movl    [rsp + #60], R11    # spill16f     movl    R11, R8 # spill172     addl    R11, #4 # int176     imull   RBX, R11    # int17a     movl    R11, R8 # spill17d     addl    R11, #3 # int181     imull   RCX, R11    # int185     movl    R10, R8 # spill188     addl    R10, #2 # int18c     imull   RDX, R10    # int190     movl    R11, R8 # spill193     incl    R11 # int196     imull   R9, R11 # int19a     addl    R9, [RSP + #32 (32-bit)]    # int19f     addl    R9, RDX # int1a2     addl    R9, RCX # int1a5     addl    R9, RBX # int1a8     addl    R9, [RSP + #60 (32-bit)]    # int1ad     addl    R9, [RSP + #56 (32-bit)]    # int1b2     addl    R9, [RSP + #52 (32-bit)]    # int1b7     addl    R9, [RSP + #48 (32-bit)]    # int1bc     movl    R10, R8 # spill1bf     addl    R10, #9 # int1c3     imull   R10, RSI    # int1c7     addl    R10, R9 # int1ca     addl    R10, RDI    # int1cd     addl    R10, [RSP + #44 (32-bit)]   # int1d2     movl    R11, R8 # spill1d5     addl    R11, #12    # int1d9     imull   R13, R11    # int1dd     addl    R13, R10    # int1e0     movl    R10, R8 # spill1e3     addl    R10, #13    # int1e7     imull   R10, [RSP + #40 (32-bit)]   # int1ed     addl    R10, R13    # int1f0     addl    RBP, R10    # int1f3     addl    R14, RBP    # int1f6     movl    R10, R8 # spill1f9     addl    R10, #16    # int1fd     cmpl    R10, #999999985204     jl     B2   # loop end  P=1.000000 C=7419903.000000

在这里

[RSP + ...]
,由于需要保留更多中间结果,因此观察到了更多的“溢出”和对堆栈的更多访问。

因此,问题的答案很简单:

2 * (i * i)
2 * i * i
第一种情况要快,因为JIT会生成更多的最佳汇编代码。


但是,显然第一版和第二版都不好。由于任何x86-64 CPU至少都支持SSE2,因此循环可以真正受益于向量化。

因此,这是优化程序的问题;通常情况下,它展开得过于猛烈,并在脚上开枪射击,而同时又错失了其他各种机会。

实际上,现代的x86-64
CPU将指令进一步细分为微操作(µop),并具有寄存器重命名,µop缓存和循环缓冲区等功能,与简单展开以达到最佳性能相比,循环优化需要更多的技巧。根据Agner
Fog的优化指南

如果平均指令长度大于4个字节,则由µop缓存引起的性能提升会非常可观。可以考虑以下优化µop缓存使用的方法:

  • 确保关键循环足够小以适合µop缓存。
  • 将最关键的循环条目和功能条目对齐32。
  • 避免不必要的循环展开。
  • 避免使用具有额外加载时间的说明
    。。。

关于这些加载时间-
即使最快的L1D命中也要花费4个周期,一个额外的寄存器和µop,所以是的,即使是对存储器的几次访问也会损害紧密循环中的性能。

但是回到矢量化的机会-
要了解它有多快,我们可以使用GCC编译类似的C应用程序,然后直接对其进行矢量化(显示为AVX2,SSE2相似)2:

  vmovdqa ymm0, YMMWORD PTR .LC0[rip]  vmovdqa ymm3, YMMWORD PTR .LC1[rip]  xor eax, eax  vpxor xmm2, xmm2, xmm2.L2:  vpmulld ymm1, ymm0, ymm0  inc eax  vpaddd ymm0, ymm0, ymm3  vpslld ymm1, ymm1, 1  vpaddd ymm2, ymm2, ymm1  cmp eax, 125000000      ; 8 calculations per iteration  jne .L2  vmovdqa xmm0, xmm2  vextracti128 xmm2, ymm2, 1  vpaddd xmm2, xmm0, xmm2  vpsrldq xmm0, xmm2, 8  vpaddd xmm0, xmm2, xmm0  vpsrldq xmm1, xmm0, 4  vpaddd xmm0, xmm0, xmm1  vmovd eax, xmm0  vzeroupper

运行时间:

  • SSE:0.24 s,或快2倍。
  • AVX:0.15秒,或3倍快。
  • AVX2:0.08 s,或快5倍。

1
要获取JIT生成的程序集输出,请获取调试JVM并运行

-XX:+PrintOptoAssembly

2 C版本使用

-fwrapv
标志进行编译,这使GCC可以将带符号整数溢出视为二进制补码。



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

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

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