【问题标题】:Does the Java optimizer memoize calculated values?Java 优化器是否记忆计算值?
【发布时间】:2014-06-18 12:44:44
【问题描述】:

哪个fizzbuzz 实现更高效?

   public static void fizzBuzz1(int n)
    {
        boolean fizzed, buzzed;
        for(int i = 1; i <= n; i++)
        {
            fizzed = buzzed = false;
            if(i % 3 == 0)
                fizzed = true;
            if(i % 5 == 0)
                buzzed = true;
            if(fizzed  && buzzed)
                System.out.println("FizzBuzz");
            else if(fizzed)
                System.out.println("Fizz");
            else if(buzzed)
                System.out.println("Buzz");
            else
                System.out.println(i);
        }
    }

    public static void fizzBuzz2(int n)
    {
        for(int i = 1; i <= n; i++)
        {
            if(i % 3 == 0  && i % 5 == 0)
                System.out.println("FizzBuzz");
            else if(i % 3 == 0)
                System.out.println("Fizz");
            else if(i % 5 == 0)
                System.out.println("Buzz");
            else
                System.out.println(i);
        }
    }

【问题讨论】:

  • println() 比使用布尔变量贵 1000 倍以上。我建议第二个,因为它更简单/
  • 我同意@Peter 的观点,与打印一行的成本相比,模运算的计算成本可以忽略不计。在您的第一个实现中,为什么不使用fizzed = i % 3 == 0
  • @PeterLawrey 我同意println() 比使用布尔变量更昂贵,但我不明白如何使用布尔变量替代println()?在我的两个实现中,println() 每次迭代都会调用一次。
  • 问题很明确:Java 优化器是否记住计算值?。这不是针对最有效、最优雅或最简单的实现,也不是针对打印是否比其他操作更昂贵,而是避免(昂贵的)模计算在这里是否有益。答案是:实践中没有。两种方法的字节码看起来不同,但是如果这被执行了几次,Java JIT 编译器将优化这些方法,因此,实际的本地代码很可能对两者都是相等的。 (无法检查此 ATM,因此只能发表评论)
  • OK, ... ~"避免 oneone boolean each 为模的昂贵计算" ;-) 如果没有人发布涉及某些 JIT 输出的答案,我会尝试这样做,但可能需要几天时间才能有机会。

标签: java optimization performance fizzbuzz


【解决方案1】:

如果你忽略println的成本,你可以使用一个开关

public static void fizzBuzz(int n) {
    for(int i = 1; i <= n; i++) {
         switch(i % 15) {
             case 0: 
                 System.out.println("FizzBuzz"); 
                 break;
             case 3: case 6: case 9: case 12: 
                 System.out.println("Fizz"); 
                 break;
             case 5: case 10: 
                 System.out.println("Buzz"); 
                 break;
             default:
                 System.out.println(i);
                 break;
         }
    }
}

【讨论】:

    【解决方案2】:

    这一次发生了一些有趣的转变。

    首先,我尝试查看使用原始方法生成的程序集。然而,JIT 做了一些内联和优化,其中包括 System.out.println 调用,因此生成的程序集输出(对我来说)太大了,无法在合理的时间内进行合理的分析。

    所以我简化了整个事情,以便能够专注于实际问题。最后,我运行了以下程序:

    class Test04
    {
        public static void main(String args[])
        {
            long sum = 0;
            for (int i=1000; i<12000; i++)
            {
                sum += fizzBuzz1(i);
                sum += fizzBuzz2(i);
            }
            System.out.println(sum);
        }
    
        public static long fizzBuzz1(int n)
        {
            long sum = 0;
            for(int i = 1; i <= n; i++)
            {
                sum += fizzBuzzCore1(i);
            }
            return sum;
        }
    
        public static long fizzBuzzCore1(int i)
        {
            boolean fizzed = false;
            boolean buzzed = false;
            if(i % 3 == 0)
                fizzed = true;
            if(i % 5 == 0)
                buzzed = true;
            if(fizzed  && buzzed)
                return 4;
            else if(fizzed)
                return 3;
            else if(buzzed)
                return 2;
            else
                return 1;
        }
    
    
        public static long fizzBuzz2(int n)
        {
            long sum = 0;
            for(int i = 1; i <= n; i++)
            {
                sum += fizzBuzzCore2(i);
            }
            return sum;
        }
    
        public static long fizzBuzzCore2(int i)
        {
            if(i % 3 == 0  && i % 5 == 0)
                return 4;
            else if(i % 3 == 0)
                return 3;
            else if(i % 5 == 0)
                return 2;
            else
                return 1;
        }
    
    }
    

    返回值旨在防止他完全优化调用,并提取旨在保持必须比较的汇编输出大小尽可能小的“核心”方法。

    (注意:当然,这些修改可能会影响优化。例如,JIT对一个方法在被认为太大之前可能具有的字节码指令的数量有一个限制要内联,-XX:MaxInlineSize=35。但是这两种方法的效果应该大致相同,因此仍然可以得出关于实际问题的所需信息。

    而且,这也不是什么大惊喜:在最后一次优化之后,两种方法的汇编代码都包含相等指令——这里是fizzBuzzCore1 的汇编作为参考:

    Decoding compiled method 0x00000000026c0090:
    Code:
    [Entry Point]
    [Verified Entry Point]
    [Constants]
      # {method} {0x0000000057260528} &apos;fizzBuzzCore1&apos; &apos;(I)J&apos; in &apos;Test04&apos;
      # parm0:    rdx       = int
      #           [sp+0x20]  (sp of caller)
      0x00000000026c01c0: sub    $0x18,%rsp
      0x00000000026c01c7: mov    %rbp,0x10(%rsp)    ;*synchronization entry
                                                    ; - Test04::fizzBuzzCore1@-1 (line 27)
    
      0x00000000026c01cc: movslq %edx,%r10
      0x00000000026c01cf: mov    %edx,%r11d
      0x00000000026c01d2: sar    $0x1f,%r11d        ;*irem
                                                    ; - Test04::fizzBuzzCore1@6 (line 29)
    
      0x00000000026c01d6: imul   $0x66666667,%r10,%r8
      0x00000000026c01dd: imul   $0x55555556,%r10,%r10
      0x00000000026c01e4: sar    $0x21,%r8
      0x00000000026c01e8: sar    $0x20,%r10
      0x00000000026c01ec: mov    %r8d,%r8d
      0x00000000026c01ef: sub    %r11d,%r8d         ;*irem
                                                    ; - Test04::fizzBuzzCore1@14 (line 31)
    
      0x00000000026c01f2: mov    %r10d,%r10d
      0x00000000026c01f5: sub    %r11d,%r10d        ;*irem
                                                    ; - Test04::fizzBuzzCore1@6 (line 29)
    
      0x00000000026c01f8: mov    %r8d,%r11d
      0x00000000026c01fb: shl    $0x2,%r11d
      0x00000000026c01ff: add    %r8d,%r11d         ;*irem
                                                    ; - Test04::fizzBuzzCore1@14 (line 31)
    
      0x00000000026c0202: mov    %r10d,%r9d
      0x00000000026c0205: shl    %r9d
      0x00000000026c0208: add    %r10d,%r9d         ;*irem
                                                    ; - Test04::fizzBuzzCore1@6 (line 29)
    
      0x00000000026c020b: cmp    %r9d,%edx
      0x00000000026c020e: jne    0x00000000026c021c  ;*ifeq
                                                    ; - Test04::fizzBuzzCore1@21 (line 33)
    
      0x00000000026c0210: cmp    %r11d,%edx
      0x00000000026c0213: jne    0x00000000026c021c  ;*ifeq
                                                    ; - Test04::fizzBuzzCore1@25 (line 33)
    
      0x00000000026c0215: mov    $0x4,%eax
      0x00000000026c021a: jmp    0x00000000026c0239  ;*iload_1
                                                    ; - Test04::fizzBuzzCore1@32 (line 35)
    
      0x00000000026c021c: cmp    %r9d,%edx
      0x00000000026c021f: jne    0x00000000026c0228  ;*ifeq
                                                    ; - Test04::fizzBuzzCore1@33 (line 35)
    
      0x00000000026c0221: mov    $0x3,%eax
      0x00000000026c0226: jmp    0x00000000026c0239
      0x00000000026c0228: cmp    %r11d,%edx
      0x00000000026c022b: jne    0x00000000026c0234  ;*ifeq
                                                    ; - Test04::fizzBuzzCore1@41 (line 37)
    
      0x00000000026c022d: mov    $0x2,%eax
      0x00000000026c0232: jmp    0x00000000026c0239
      0x00000000026c0234: mov    $0x1,%eax          ;*irem
                                                    ; - Test04::fizzBuzzCore1@6 (line 29)
    
      0x00000000026c0239: add    $0x10,%rsp
      0x00000000026c023d: pop    %rbp
      0x00000000026c023e: test   %eax,-0x2470244(%rip)        # 0x0000000000250000
                                                    ;   {poll_return}
      0x00000000026c0244: retq   
      0x00000000026c0245: hlt    
      0x00000000026c0246: hlt    
      0x00000000026c0247: hlt    
      0x00000000026c0248: hlt    
      0x00000000026c0249: hlt    
      0x00000000026c024a: hlt    
      0x00000000026c024b: hlt    
      0x00000000026c024c: hlt    
      0x00000000026c024d: hlt    
      0x00000000026c024e: hlt    
      0x00000000026c024f: hlt    
      0x00000000026c0250: hlt    
      0x00000000026c0251: hlt    
      0x00000000026c0252: hlt    
      0x00000000026c0253: hlt    
      0x00000000026c0254: hlt    
      0x00000000026c0255: hlt    
      0x00000000026c0256: hlt    
      0x00000000026c0257: hlt    
      0x00000000026c0258: hlt    
      0x00000000026c0259: hlt    
      0x00000000026c025a: hlt    
      0x00000000026c025b: hlt    
      0x00000000026c025c: hlt    
      0x00000000026c025d: hlt    
      0x00000000026c025e: hlt    
      0x00000000026c025f: hlt    
    [Exception Handler]
    [Stub Code]
      0x00000000026c0260: jmpq   0x000000000261c560  ;   {no_reloc}
    [Deopt Handler Code]
      0x00000000026c0265: callq  0x00000000026c026a
      0x00000000026c026a: subq   $0x5,(%rsp)
      0x00000000026c026f: jmpq   0x00000000025f6f40  ;   {runtime_call}
      0x00000000026c0274: hlt    
      0x00000000026c0275: hlt    
      0x00000000026c0276: hlt    
      0x00000000026c0277: hlt    
    

    但是……

    ...可能令人惊讶的是:它根本不计算模运算!

    至少,不是明确的:这段代码中没有出现idiv 指令!因此,JIT 确实努力避免代价高昂的划分,通过一些令人讨厌的、令人讨厌的小技巧:说明

      0x00000000026c01d6: imul   $0x66666667,%r10,%r8
      0x00000000026c01dd: imul   $0x55555556,%r10,%r10
      0x00000000026c01e4: sar    $0x21,%r8
      0x00000000026c01e8: sar    $0x20,%r10
      (and following...)
    

    是除法的“无除法”实现。比如方法

    private static int divideBy3(int n)
    {
        long r10 = n;
        r10 *= 0x55555556L;
        r10 >>>= 0x20;
        long r10d = r10 & 0xFFFFFFFFL;
        return (int)r10d;
    }
    

    使用这些神奇的常数和移位来计算除以 3(类似地,对于 5 和另一个常数)。我自己没有做数学计算,但是可以在Page 32 of the "INTEGER DIVISION BY CONSTANTS" document from Hacker's Delight 找到关于如何推导出模运算的说明。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-30
      • 2021-05-23
      • 2018-07-08
      • 2012-05-27
      • 1970-01-01
      相关资源
      最近更新 更多