对JDK-lang包里一个方法的改进

作者: laosijikaichele | 来源:发表于2018-03-16 10:09 被阅读233次

    JDK-lang包里的一个方法:

    java.lang.Integer.numberOfLeadingZeros(int)
    

    这个方法是用来计算int的二进制值从左到右有连续多少个0。
    我们知道Java里的int型是有负数的,负数的二进制第1位肯定是1,所以如果参数i小于0,应该直接返回0就可以了。
    但我们看JDK里的实现:

    public static int numberOfLeadingZeros(int i) {
            // HD, Figure 5-6
            if (i == 0)
                return 32;
            int n = 1;
            if (i >>> 16 == 0) { n += 16; i <<= 16; }
            if (i >>> 24 == 0) { n +=  8; i <<=  8; }
            if (i >>> 28 == 0) { n +=  4; i <<=  4; }
            if (i >>> 30 == 0) { n +=  2; i <<=  2; }
            n -= i >>> 31;
            return n;
        }
    

    可以看到JDK里的实现里并没有对负数的情况进行判断,而是走了下面4个if分支判断和位移操作,这在参数i 为负数的情况下肯定是不必要的,而且也会影响性能。

    如果继续挖掘的话,可以看到代码里有一行注释:

    // HD, Figure 5-6

    注释里HD的意思是指《Hacker's Delight》这本书,意思是该方法的实现逻辑参考了这本书,那我们就来看下《Hacker's Delight》书里的实现代码:

    int nlz(unsigned x) {
    int n;
    if (x == 0) return(32);
    n = 1;
    if ((x >> 16) == 0) {n = n +16; x = x <<16;}
    if ((x >> 24) == 0) {n = n + 8; x = x << 8;}
    if ((x >> 28) == 0) {n = n + 4; x = x << 4;}
    if ((x >> 30) == 0) {n = n + 2; x = x << 2;}
    n = n - (x >> 31);
    return n;
    }
    

    可以看到这是C语言的实现,乍一看,貌似和JDK里的Java实现是一样的,但仔细看,你会发现方法参数x是unsigned类型的,因为unsigned类型不会有负数,不需要判断负数的情况,所以《Hacker's Delight》书里的实现是没问题的,而JDK实现里的方法参数是int类型,是需要判断负数的情况的。

    所以我们可以大胆的推测,JDK的程序员当初在实现这个方法的时候,只是把
    《Hacker's Delight》书里的实现改为了Java实现,而没有考虑到参数类型的区别。

    这个方法在JDK里是被标记为intrinsic的,意思是该方法针对不同的硬件有更底层的原语级的实现,但这并不代表Java实现的逻辑就不需要被优化,因为:
    1.在有些硬件上可能没有intrinsic支持。
    2.用户显式关闭了intrinsic调用。
    3.没有开启JIT编译。

    除了Integer类之外,Long类里也有相同功能的方法需要改进:

    java.lang.Long.numberOfLeadingZeros(long) 
    

    我给OpenJDK提了bug,其实应该叫改进/增强(Enhancement),OpenJDK官方经过测试,发现修改后的方案确实会提升性能,于是愉快的修复了这个问题:https://bugs.openjdk.java.net/browse/JDK-8189230
    不过大家可能要等到Java-11发布的时候才能看到这个修改了。:)

    相关文章

      网友评论

        本文标题:对JDK-lang包里一个方法的改进

        本文链接:https://www.haomeiwen.com/subject/pfllqftx.html