计算无符号长整数序列中的公共位

时间:2009-10-05 01:04:26

标签: c# bit-manipulation

我正在寻找比以下更快的算法。给定一系列64位无符号整数,返回序列中设置的64位中每一位的次数。

示例:

4608 = 0000000000000000000000000000000000000000000000000001001000000000 
4097 = 0000000000000000000000000000000000000000000000000001000000000001
2048 = 0000000000000000000000000000000000000000000000000000100000000000

counts 0000000000000000000000000000000000000000000000000002101000000001

示例:

2560 = 0000000000000000000000000000000000000000000000000000101000000000
530  = 0000000000000000000000000000000000000000000000000000001000010010
512  = 0000000000000000000000000000000000000000000000000000001000000000

counts 0000000000000000000000000000000000000000000000000000103000010010

目前我正在使用一种相当明显和天真的方法:

static int bits = sizeof(ulong) * 8;

public static int[] CommonBits(params ulong[] values) {
    int[] counts = new int[bits];
    int length = values.Length;

    for (int i = 0; i < length; i++) {
        ulong value = values[i];
        for (int j = 0; j < bits && value != 0; j++, value = value >> 1) {
            counts[j] += (int)(value & 1UL);
        }
    }

    return counts;
}

8 个答案:

答案 0 :(得分:1)

通过首先对整数进行“或”操作,然后使用结果确定需要检查哪些位,可以实现小的速度提升。你仍然需要迭代每一位,但只能在没有1的位上迭代一次,而不是values.Length次。

答案 1 :(得分:0)

我将引导您进入经典:Bit Twiddling Hacks,但您的目标似乎与典型的计数略有不同(即您的'计数'变量是一种非常奇怪的格式),但也许它会有用

答案 2 :(得分:0)

好的,让我再试一次:D

通过在lef

中将每个位移位n * 8,将64位整数中的每个字节更改为64位整数 例如

10110101 - &gt; 0000000100000000000000010000000100000000000000010000000000000001 (使用查找表进行翻译)

然后只需要以正确的方式对所有内容进行求和,并得到一组无符号字符整数。

你必须制作8 *(64位整数)sumations

c中的代码

//LOOKTABLE IS EXTERNAL and has is int64[256] ;
unsigned char* bitcounts(int64* int64array,int len)
{  
    int64* array64;
    int64 tmp;
    unsigned char* inputchararray;
    array64=(int64*)malloc(64);
    inputchararray=(unsigned char*)input64array;
    for(int i=0;i<8;i++) array64[i]=0; //set to 0

    for(int j=0;j<len;j++)
    {             
         tmp=int64array[j];
         for(int i=7;tmp;i--)
         {
             array64[i]+=LOOKUPTABLE[tmp&0xFF];
             tmp=tmp>>8;
         }
    }
    return (unsigned char*)array64;
}

与原始实施例相比,这个红移速度是因子8,因为它每次都会有8位。

编辑:

我修复了代码以便在较小的整数上进行更快的中断,但我仍然不确定endianess 这仅适用于最多256个输入,因为它使用无符号字符来存储数据。如果您有更长的输入字符串,您可以更改此代码以容纳最多2 ^ 16个bitcounts并减少spped 2

答案 3 :(得分:0)

我能在这里做的最好的事情就是变得愚蠢并且展开内循环...似乎已经将性能降低了一半(大约4秒,而不是你的8个处理100个超过100,000次) ...我使用了一个qick命令行应用程序来生成以下代码:

for (int i = 0; i < length; i++)
{
    ulong value = values[i];
    if (0ul != (value & 1ul)) counts[0]++;
    if (0ul != (value & 2ul)) counts[1]++;
    if (0ul != (value & 4ul)) counts[2]++;
    //etc...
    if (0ul != (value & 4611686018427387904ul)) counts[62]++;
    if (0ul != (value & 9223372036854775808ul)) counts[63]++;
}

这是我能做的最好的...根据我的评论,你会浪费一些(我知道不知道多少)在32位环境中运行它。如果您关注性能,可能让您首先将数据转换为uint。

棘手的问题......甚至可能会让你把它编组到C ++中,但这完全取决于你的应用程序。对不起,我无法提供帮助,也许别人会看到我错过的东西。

更新,更多的探查器会议显示稳定的36%改善。 耸肩我试过了。

答案 4 :(得分:0)

const unsigned int BYTESPERVALUE = 64 / 8;
unsigned int bcount[BYTESPERVALUE][256];
memset(bcount, 0, sizeof bcount);
for (int i = values.length; --i >= 0; )
  for (int j = BYTESPERVALUE ; --j >= 0; ) {
    const unsigned int jth_byte = (values[i] >> (j * 8)) & 0xff;
    bcount[j][jth_byte]++; // count byte value (0..255) instances
  }

unsigned int count[64];
memset(count, 0, sizeof count);
for (int i = BYTESPERVALUE; --i >= 0; )
  for (int j = 256; --j >= 0; ) // check each byte value instance
    for (int k = 8; --k >= 0; ) // for each bit in a given byte
      if (j & (1 << k)) // if bit was set, then add its count
        count[i * 8 + k] += bcount[i][j];

答案 5 :(得分:0)

另一种可能有利可图的方法是构建一个包含256个元素的数组, 它编码了递增计数数组时需要采取的操作。

这是一个4元素表的示例,它使用2位而不是8位。

int bitToSubscript[4][3] =
{
    {0},       // No Bits set
    {1,0},     // Bit 0 set
    {1,1},     // Bit 1 set
    {2,0,1}    // Bit 0 and bit 1 set.
}

然后算法退化为:

  • 从数字中选择2个右手位。
  • 使用它作为一个小整数来索引bitToSubscriptArray。
  • 在该数组中,拉出第一个整数。这是count数组中需要递增的元素数。
  • 根据该计数,根据您从bitToSubscript数组中提取的下标,迭代行的其余部分,递增计数。
  • 完成该循环后,将原始数字向右移动两位......根据需要冲洗重复。

现在我在这个描述中忽略了一个问题。实际的下标是相对的。您需要跟踪计数数组中的位置。每次循环时,都会向偏移量添加两个。对于该偏移量,您可以添加bitToSubscript数组中的相对下标。

根据这个小例子,应该可以扩展到你想要的大小。我认为可以使用另一个程序来生成bitToSubscript数组的源代码,这样它就可以在你的程序中进行简单的硬编码。

这个方案还有其他的变化,但我希望它的平均运行速度比一次一点的运行速度快。

好狩猎。

答案 6 :(得分:0)

我相信这应该可以提高速度:

  const ulong mask = 0x1111111111111111;
  public static int[] CommonBits(params ulong[] values)
  {
    int[] counts = new int[64];

    ulong accum0 = 0, accum1 = 0, accum2 = 0, accum3 = 0;

    int i = 0;
    foreach( ulong v in values ) {
      if (i == 15) {
        for( int j = 0; j < 64; j += 4 ) {
          counts[j]   += ((int)accum0) & 15;
          counts[j+1] += ((int)accum1) & 15;
          counts[j+2] += ((int)accum2) & 15;
          counts[j+3] += ((int)accum3) & 15;
          accum0 >>= 4;
          accum1 >>= 4;
          accum2 >>= 4;
          accum3 >>= 4;
        }
        i = 0;
      }

      accum0 += (v)      & mask;
      accum1 += (v >> 1) & mask;
      accum2 += (v >> 2) & mask;
      accum3 += (v >> 3) & mask;
      i++;
    }

    for( int j = 0; j < 64; j += 4 ) {
      counts[j]   += ((int)accum0) & 15;
      counts[j+1] += ((int)accum1) & 15;
      counts[j+2] += ((int)accum2) & 15;
      counts[j+3] += ((int)accum3) & 15;
      accum0 >>= 4;
      accum1 >>= 4;
      accum2 >>= 4;
      accum3 >>= 4;
    }

    return counts;
  }

演示:http://ideone.com/eNn4O(需要更多测试用例)

答案 7 :(得分:-1)

http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetNaive

其中一个

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v
for (c = 0; v; c++)
{
  v &= v - 1; // clear the least significant bit set
}

请记住,此方法的复杂性为aprox O(log2(n)),其中n是计数位数,因此对于10个二进制,它只需要2个循环

你应该把metod用于计算32位的64位算术,并将它应用于每一半的单词,2 * 15 + 4指令需要什么

// option 3, for at most 32-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % 
   0x1f;
c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;

如果您有支持sse4,3的处理器,则可以使用POPCNT指令。 http://en.wikipedia.org/wiki/SSE4