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

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发布的时候才能看到这个修改了。:)

    原文作者:laosijikaichele
    原文地址: https://www.jianshu.com/p/7a6a8527ec36
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞