长按快速按位运算

时间:2017-01-04 18:47:00

标签: java performance bit-shift

我知道我可以编写以下方法来计算long的设置位索引:

private static List<Integer> bitPositions(long number) {
    final List<Integer> positions = new ArrayList<>();
    int position = 1;
    while (number != 0) {
        if ((number & 1L) != 0) {
            positions.add(position);
        }
        position++;
        number = number >>> 1;
    }
    return positions;
}

我的问题是:有更快的方法吗

3 个答案:

答案 0 :(得分:4)

最快的方法

BitBank's answer to this question的速度大约是这个答案的两个方法的两倍。这在BitBank的答案中窃取了这个想法并使其比我在我的机器上快了73%(比问题的方法快9倍),通过使用bit twiddling来重复关闭最不重要的一位而不是右移一位离开右端并跟踪发生了多少变化。

private static final byte[] bitPositions(long n) {
    final byte[] result = new byte[Long.bitCount(n)];

    for (int i = 0; n != 0L; i++) {
        result[i] = (byte) ((byte) Long.numberOfTrailingZeros(n) + 1);
        n &= n - 1L;  // Change least-significant one bit to a zero bit.
    }

    return result;
}

BitBank答案的改进

  • 无需跟踪我们跳过的位数。
  • 快速将最后一位变为零位。
  • byte的双重加速可以加快速度。我认为这是因为它允许byte大小而不是int大小的算术。

手动融合

正如Durandal在问题评论中指出的那样,您可以交换以下内容:

for (int bitPosition : bitPositions(n)) {
    // Do something with `bitPosition`.
}

对于跳过方法调用的样式而不是这样做:

long temp = n;
while (temp != 0L) {
    int bitPosition = Long.numberOfTrailingZeros(temp) + 1;
    temp &= temp - 1L;  // Change least-significant one bit to a zero bit.

    // Do something with `bitPosition`.
}

融合的好处

  • 没有浪费时间调用方法。
  • 无需创建或垃圾收集数组,节省时间和内存。
  • 在您使用它的整个过程中,位位置可能会保留在非常快的CPU寄存器中,而不是可能需要将其写入RAM中的数组(速度要慢得多),然后稍后从RAM中读取它。上。

融合的缺点

  • 这比使用一个明确命名的方法调用并干净利用一系列结果有点丑陋。
  • 如果代码中有多个位置需要计算数字的位位置,则必须在每个位置重复代码(违反DRY)。
  • 如果要通过相同数字的位位置多次迭代迭代,则必须重新计算位位置,而不是重复使用先前生成的数组。

    但这可能不是一个实际的缺点,如果重新计算位位置比从RAM中的数组加载预先计算的位置更快。

最慢的方法

这是一种产生相同结果的方法(仅在byte[]而不是List<Integer>)大约快两倍:

private static final byte[] bitPositions(long n) {
    final byte[] result = new byte[Long.bitCount(n)];

    int i = 0;
    for (byte bit = 1; n != 0L; bit++) {
        if ((n & 1L) != 0) result[i++] = bit;
        n >>>= 1;
    }

    return result;
}

我建议将byte bit = 1循环中的for更改为byte bit = 0,以切换到从0开始而不是以1开头编号位位置的传统方法。

改进

  • 使用Long.bitCount(n)预先计算所需的容量(使用处理器中非常快速的“popcount”指令)可以大大加快您的方法速度。您可以使用ArrayList
  • 制作new ArrayList<>(Long.bitCount(n))来更改此设置
  • 使用ArrayList<Integer>byte[]慢,因为:
    • 必须浪费时间从the Integer cache查找低值(-127128Integer值,以便将它们放入ArrayList
    • 以后使用int生成的List<Integer>时必须浪费时间,因为您必须同时从Integer检索List<Integer>然后检索来自int。{/ li>的Integer
  • byte[]使用大约1/4(32位系统)或1/8(64位系统)ArrayList<Integer>的内存,因为byte是那么多小于指向Integer s。
  • 的指针

比最慢的方法快一点,但更丑陋

正如另一个人删除的答案所暗示的那样,循环展开会加快我的机器上的速度(请检查您的机器上是否也是如此):

private static final byte[] bitPositions(final long n) {
    final byte[] result = new byte[Long.bitCount(n)];

    int i = 0;
    if ((n &                    1L) != 0L) result[i++] = 1;
    if ((n &                    2L) != 0L) result[i++] = 2;
    if ((n &                    4L) != 0L) result[i++] = 3;
    if ((n &                    8L) != 0L) result[i++] = 4;
    if ((n &                   16L) != 0L) result[i++] = 5;
    if ((n &                   32L) != 0L) result[i++] = 6;
    if ((n &                   64L) != 0L) result[i++] = 7;
    if ((n &                  128L) != 0L) result[i++] = 8;
    if ((n &                  256L) != 0L) result[i++] = 9;
    if ((n &                  512L) != 0L) result[i++] = 10;
    if ((n &                 1024L) != 0L) result[i++] = 11;
    if ((n &                 2048L) != 0L) result[i++] = 12;
    if ((n &                 4096L) != 0L) result[i++] = 13;
    if ((n &                 8192L) != 0L) result[i++] = 14;
    if ((n &                16384L) != 0L) result[i++] = 15;
    if ((n &                32768L) != 0L) result[i++] = 16;
    if ((n &                65536L) != 0L) result[i++] = 17;
    if ((n &               131072L) != 0L) result[i++] = 18;
    if ((n &               262144L) != 0L) result[i++] = 19;
    if ((n &               524288L) != 0L) result[i++] = 20;
    if ((n &              1048576L) != 0L) result[i++] = 21;
    if ((n &              2097152L) != 0L) result[i++] = 22;
    if ((n &              4194304L) != 0L) result[i++] = 23;
    if ((n &              8388608L) != 0L) result[i++] = 24;
    if ((n &             16777216L) != 0L) result[i++] = 25;
    if ((n &             33554432L) != 0L) result[i++] = 26;
    if ((n &             67108864L) != 0L) result[i++] = 27;
    if ((n &            134217728L) != 0L) result[i++] = 28;
    if ((n &            268435456L) != 0L) result[i++] = 29;
    if ((n &            536870912L) != 0L) result[i++] = 30;
    if ((n &           1073741824L) != 0L) result[i++] = 31;
    if ((n &           2147483648L) != 0L) result[i++] = 32;
    if ((n &           4294967296L) != 0L) result[i++] = 33;
    if ((n &           8589934592L) != 0L) result[i++] = 34;
    if ((n &          17179869184L) != 0L) result[i++] = 35;
    if ((n &          34359738368L) != 0L) result[i++] = 36;
    if ((n &          68719476736L) != 0L) result[i++] = 37;
    if ((n &         137438953472L) != 0L) result[i++] = 38;
    if ((n &         274877906944L) != 0L) result[i++] = 39;
    if ((n &         549755813888L) != 0L) result[i++] = 40;
    if ((n &        1099511627776L) != 0L) result[i++] = 41;
    if ((n &        2199023255552L) != 0L) result[i++] = 42;
    if ((n &        4398046511104L) != 0L) result[i++] = 43;
    if ((n &        8796093022208L) != 0L) result[i++] = 44;
    if ((n &       17592186044416L) != 0L) result[i++] = 45;
    if ((n &       35184372088832L) != 0L) result[i++] = 46;
    if ((n &       70368744177664L) != 0L) result[i++] = 47;
    if ((n &      140737488355328L) != 0L) result[i++] = 48;
    if ((n &      281474976710656L) != 0L) result[i++] = 49;
    if ((n &      562949953421312L) != 0L) result[i++] = 50;
    if ((n &     1125899906842624L) != 0L) result[i++] = 51;
    if ((n &     2251799813685248L) != 0L) result[i++] = 52;
    if ((n &     4503599627370496L) != 0L) result[i++] = 53;
    if ((n &     9007199254740992L) != 0L) result[i++] = 54;
    if ((n &    18014398509481984L) != 0L) result[i++] = 55;
    if ((n &    36028797018963968L) != 0L) result[i++] = 56;
    if ((n &    72057594037927936L) != 0L) result[i++] = 57;
    if ((n &   144115188075855872L) != 0L) result[i++] = 58;
    if ((n &   288230376151711744L) != 0L) result[i++] = 59;
    if ((n &   576460752303423488L) != 0L) result[i++] = 60;
    if ((n &  1152921504606846976L) != 0L) result[i++] = 61;
    if ((n &  2305843009213693952L) != 0L) result[i++] = 62;
    if ((n &  4611686018427387904L) != 0L) result[i++] = 63;
    if ((n & -9223372036854775808L) != 0L) result[i++] = 64;

    return result;
}

您也可以更改此项以开始计数位位置为零而不是一位。

改进

  • 无需在号码上重复执行>>>
  • 避免在位位置上重复执行++
  • 避免需要检查数字是否已达到零。
  • 避免某些分支误预测。

答案 1 :(得分:3)

如果您不介意使用内在函数,则可以使用更快的版本。 Long.numberOfTrailingZeros()将使用counts the number of consecutive zero bits starting from the least-significant bit(x86处理器上的BSF instruction)的内在CPU。

对于稀疏值,这将比所有其他循环方法更快,因为它在主循环中没有任何条件或分支,它通过单次迭代跳过任意数量的0的运行,并且对于64位longBSF内在函数在英特尔Haswell CPU上有a latency of only 3 clock cycles

private static final byte[] bitPositions(long n) {
    final byte[] result = new byte[Long.bitCount(n)];

    byte bitPosition = 0;
    for (int i = 0; n != 0L; i++) {
        final byte bitsToSkip = (byte) (Long.numberOfTrailingZeros(n) + 1);
        n >>>= bitsToSkip;
        bitPosition += bitsToSkip;
        result[i] = bitPosition;
    }

    return result;
}

答案 2 :(得分:1)

这里我使用了一种简单的方法,即次线性时间。您决定是从左侧算起还是从右侧算起,在printf("%ld\n", 32-pos)中,您可以考虑32-pospos

bit_pos(unsigned long x)
{
    unsigned long pos;
    unsigned long w;
    while(x) {
        /* extract the rightmost `1` in `w` then remove it */
        w = x&-x;
        x-=w;

        if (w) {
            /* compute number of trailing zeros (location of 1) for w */
            pos = 1;
            if (!(w >> 16)) {pos+=16;w<<=16;}
            if (!(w >> 24)) {pos+= 8;w<<= 8;}
            if (!(w >> 28)) {pos+= 4;w<<= 4;}
            if (!(w >> 30)) {pos+= 2;w<<= 2;}
            pos = pos - (w >> 31);
            printf("%ld\n", 32-pos);
        }
    }
    printf("\n");
}

main()
{
    bit_pos(2UL+4+32+1024);
    bit_pos(3456789UL);
}