TODO-FIXME:在Java 8的Integer类中?

时间:2018-06-28 11:33:40

标签: java integer

在阅读Java 8的Integer类时,我遇到了以下FIX-ME:(第379行)

// TODO-FIXME: convert (x * 52429) into the equiv shift-add
// sequence.

整个评论为:

// I use the "[invariant division by multiplication][2]" trick to
// accelerate Integer.toString.  In particular we want to
// avoid division by 10.
//
// The "trick" has roughly the same performance characteristics
// as the "classic" Integer.toString code on a non-JIT VM.
// The trick avoids .rem and .div calls but has a longer code
// path and is thus dominated by dispatch overhead.  In the
// JIT case the dispatch overhead doesn't exist and the
// "trick" is considerably faster than the classic code.
//
// TODO-FIXME: convert (x * 52429) into the equiv shift-add
// sequence.
//
// RE:  Division by Invariant Integers using Multiplication
//      T Gralund, P Montgomery
//      ACM PLDI 1994
//

我无法想象我应该为此担心,因为它已经存在了很长时间。

但是,有人可以阐明FIX-ME的含义吗,并且有副作用吗?


旁注:

  • 我看到这已从JDK 10中删除
  • 链接中引用的paper似乎并没有解决直接解决该问题的方法。

1 个答案:

答案 0 :(得分:17)

52429是最接近(2 ^ 19)/ 10的整数,因此可以通过将乘以与52429相乘再除以2 ^ 19(其中后者是微不足道的)来实现除以10移位操作,而不需要完全除法。

代码作者似乎建议按照此(C语言)代码段,使用移位/加法运算可以更优化地完成乘法:

uint32_t div10(uint16_t in)
{
    // divides by multiplying by 52429 / (2 ^ 16)
    // 52429 = 0xcccd
    uint32_t x = in << 2;    // multiply by 4   : total = 0x0004
    x += (x << 1);           // multiply by 3   : total = 0x000c
    x += (x << 4);           // multiply by 17  : total = 0x00cc
    x += (x << 8);           // multiply by 257 : total = 0xcccc
    x += in;                 // one more makes  : total = 0xcccd

    return x >> 19;
}

我无法回答的是为什么他们显然认为这可能比Java环境中的直接乘法更好。

在机器代码级别上,只有在没有硬件乘法器的(当今很少见)CPU上才是最佳选择,在这种情况下,最简单的(尽管也许很简单)乘法功能将需要16个移位/加法运算才能将两个16位数字相乘。

另一方面,如上所述的手工函数可以通过利用该常数的数值属性,以较少的步骤执行与该常数的乘法,在这种情况下,将其减少为四个移位/加法运算,而不是16。 / p>

即使仅带有-O1优化标志,FWIW(和令人印象深刻的)macOS上的clang编译器实际上会将上面的代码转换回一个乘法:

_div10:                             ## @div10
    pushq   %rbp
    movq    %rsp, %rbp
    imull   $52429, %edi, %eax      ## imm = 0xCCCD
    shrl    $19, %eax
    popq    %rbp
    retq

它也变成:

uint32_t div10(uint16_t in) {
   return in / 10;
}

完全等同于 汇编代码,这恰恰表明现代编译器确实了解得最多。