Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/133.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 如何在Sandy Bridge上的一系列INT中快速地将位计数到单独的箱子中?_C++_Assembly_X86_Simd_Avx - Fatal编程技术网

C++ 如何在Sandy Bridge上的一系列INT中快速地将位计数到单独的箱子中?

C++ 如何在Sandy Bridge上的一系列INT中快速地将位计数到单独的箱子中?,c++,assembly,x86,simd,avx,C++,Assembly,X86,Simd,Avx,更新:请阅读代码,它不是关于在一个整数中计算位的问题 有可能用一些聪明的汇编程序来提高以下代码的性能吗 uint bit_counter[64]; void Count(uint64 bits) { bit_counter[0] += (bits >> 0) & 1; bit_counter[1] += (bits >> 1) & 1; // .. bit_counter[63] += (bits >> 63) & 1

更新:请阅读代码,它不是关于在一个整数中计算位的问题

有可能用一些聪明的汇编程序来提高以下代码的性能吗

uint bit_counter[64];

void Count(uint64 bits) {
  bit_counter[0] += (bits >> 0) & 1;
  bit_counter[1] += (bits >> 1) & 1;
  // ..
  bit_counter[63] += (bits >> 63) & 1;
}
Count
在我的算法的最内部循环中

更新: 体系结构:x86-64,Sandy Bridge,因此可以使用SSE4.2、AVX1和旧技术,但不能使用AVX2或BMI1/2


变量几乎有随机位(接近半个零和半个一)

通常无法回答这个问题;这完全取决于编译器 以及底层架构。唯一真正知道的方法就是尝试 不同的解决方案和措施。(例如,在某些机器上, 轮班可能非常昂贵。对于其他人来说,不是。)首先,我会使用 比如:

uint64_t mask = 1;
int index = 0;
while ( mask != 0 ) {
    if ( (bits & mask) != 0 ) {
        ++ bit_counter[index];
    }
    ++ index;
    mask <<= 1;
}
可能更好。或者更糟。。。事先知道是不可能的。它是 也可能在某些机器上,系统地切换到 低阶位和掩蔽,正如你们所做的,将是最好的

一些优化还取决于典型数据的外观。如果 大多数单词只设置了一个或两个位,您可以通过 一次测试一个字节,或一次测试四位,并跳过这些 这些都是零。

看看

  • 计数位集
编辑至于“位位置桶累积”(
位计数器[]
),我感觉这可能是valarrays+掩蔽的一个好例子。不过,这需要相当多的编码+测试+评测。如果你真的感兴趣,请告诉我


现在,您可以使用绑定元组(TR1、boost或C++11)非常接近valarray行为;我有一种感觉,读起来会更简单,编译起来也会更慢。

也许你可以一次做8个,方法是取8位,间隔8,并保留8个uint64作为计数。但是,每个计数器只有1个字节,因此在必须解包那些uint64之前,您只能累计255次调用
count

您可以尝试使用SSE,每次迭代增加4个元素

警告:未测试的代码如下

#include <stdint.h>
#include <emmintrin.h>

uint32_t bit_counter[64] __attribute__ ((aligned(16)));
                     // make sure bit_counter array is 16 byte aligned for SSE

void Count_SSE(uint64 bits)
{
    const __m128i inc_table[16] = {
        _mm_set_epi32(0, 0, 0, 0),
        _mm_set_epi32(0, 0, 0, 1),
        _mm_set_epi32(0, 0, 1, 0),
        _mm_set_epi32(0, 0, 1, 1),
        _mm_set_epi32(0, 1, 0, 0),
        _mm_set_epi32(0, 1, 0, 1),
        _mm_set_epi32(0, 1, 1, 0),
        _mm_set_epi32(0, 1, 1, 1),
        _mm_set_epi32(1, 0, 0, 0),
        _mm_set_epi32(1, 0, 0, 1),
        _mm_set_epi32(1, 0, 1, 0),
        _mm_set_epi32(1, 0, 1, 1),
        _mm_set_epi32(1, 1, 0, 0),
        _mm_set_epi32(1, 1, 0, 1),
        _mm_set_epi32(1, 1, 1, 0),
        _mm_set_epi32(1, 1, 1, 1)
    };

    for (int i = 0; i < 64; i += 4)
    {
        __m128i vbit_counter = _mm_load_si128(&bit_counter[i]);
                                          // load 4 ints from bit_counter
        int index = (bits >> i) & 15;     // get next 4 bits
        __m128i vinc = inc_table[index];  // look up 4 increments from LUT
        vbit_counter = _mm_add_epi32(vbit_counter, vinc);
                                          // increment 4 elements of bit_counter
        _mm_store_si128(&bit_counter[i], vbit_counter);
    }                                     // store 4 updated ints
}
#包括
#包括
uint32位计数器[64]uuuu属性_uuu((对齐(16));
//确保位_计数器阵列与SSE的16字节对齐
无效计数(uint64位)
{
常数m128i inc_表[16]={
_mm_集_epi32(0,0,0,0),
_mm_集_epi32(0,0,0,1),
_mm_集_epi32(0,0,1,0),
_mm_set_epi32(0,0,1,1),
_mm_集_epi32(0,1,0,0),
_mm_set_epi32(0,1,0,1),
_mm_set_epi32(0,1,1,0),
_mm_集_epi32(0,1,1,1),
_mm_集_epi32(1,0,0,0),
_mm_集_epi32(1,0,0,1),
_mm_set_epi32(1,0,1,0),
_mm_集_epi32(1,0,1,1),
_mm_集_epi32(1,1,0,0),
_mm_集_epi32(1,1,0,1),
_mm_set_epi32(1,1,1,0),
_mm_set_epi32(1,1,1,1)
};
对于(int i=0;i<64;i+=4)
{
__m128i vbit_计数器=_mm_load_si128(&bit_计数器[i]);
//从位计数器加载4个整数
int index=(位>>i)&15;//获取下一个4位
__m128i vinc=inc_table[index];//从LUT查找4个增量
vbit_计数器=_mm_add_epi32(vbit_计数器,vinc);
//增量4位计数器的元素
_mm_存储_si128(&bit_计数器[i],vbit_计数器);
}//存储4个更新的整数
}
工作原理:基本上,我们在这里所做的就是对原始循环进行矢量化,这样我们每个循环迭代处理4位,而不是1位。我们现在有16次循环迭代,而不是64次。对于每个迭代,我们从
中加载4位,然后将它们用作LUT的索引,LUT包含当前4位的4个增量的所有可能组合。然后,我们将这4个增量添加到位u计数器的当前4个元素中


加载、存储和添加的数量减少了4倍,但这将在一定程度上被LUT加载和其他内务处理所抵消。不过,您可能仍然会看到2倍的速度。如果您决定尝试,我很想知道结果。

如果您计算每个半字节(16种可能性)在每个偏移(16种可能性)处出现的频率,您可以轻松地对结果求和。这256笔款项很容易保存:

unsigned long nibble_count[16][16]; // E.g. 0x000700B0 corresponds to [4][7] and [2][B]
unsigned long bitcount[64];

void CountNibbles(uint64 bits) {
  // Count nibbles
  for (int i = 0; i != 16; ++i) {
     nibble_count[i][bits&0xf]++;
     bits >>= 4;
  }
}
void SumNibbles() {
  for (int i = 0; i != 16; ++i) {
    for (int nibble = 0; nibble != 16; ++nibble) {
        for(int bitpos = 0; bitpos != 3; ++bitpos) {
           if (nibble & (1<<bitpos)) {
              bitcount[i*4 + bitpos] += nibble_count[i][nibble];
           }
        }
     }
   }
}
无符号长半字节计数[16][16];//例如,0x000700B0对应于[4][7]和[2][B]
无符号长位计数[64];
无效可数字节(uint64位){
//数一小口
对于(int i=0;i!=16;++i){
半字节计数[i][bits&0xf]++;
位>>=4;
}
}
void sumnibles(){
对于(int i=0;i!=16;++i){
for(int半字节=0;半字节!=16;++半字节){
用于(int-bitpos=0;bitpos!=3;++bitpos){

如果(nibble&(1你可以像这样展开你的函数。它可能比你的编译器能做的更快

//   rax as 64 bit input
   xor  rcx, rcx                //clear addent

   add  rax, rax                //Copy 63th bit to carry flag
   adc  dword ptr [@bit_counter + 63 * 4], ecx    //Add carry bit to counter[64]

   add  rax, rax                //Copy 62th bit to carry flag
   adc  dword ptr [@bit_counter + 62 * 4], ecx    //Add carry bit to counter[63]

   add  rax, rax                //Copy 62th bit to carry flag
   adc  dword ptr [@bit_counter + 61 * 4], ecx    //Add carry bit to counter[62]
//   ...
   add  rax, rax                //Copy 1th bit to carry flag
   adc  dword ptr [@bit_counter + 1 * 4], ecx     //Add carry bit to counter[1]

   add  rax, rax                //Copy 0th bit to carry flag
   adc  dword ptr [@bit_counter], ecx             //Add carry bit to counter[0]
编辑:

您也可以尝试使用双增量,如下所示:

//   rax as 64 bit input
   xor  rcx, rcx                //clear addent
//
   add  rax, rax                //Copy 63th bit to carry flag
   rcl  rcx, 33                 //Mov carry to 32th bit as 0bit of second uint
   add  rax, rax                //Copy 62th bit to carry flag
   adc  qword ptr [@bit_counter + 62 * 8], rcx  //Add rcx to 63th and 62th counters

   add  rax, rax                //Copy 61th bit to carry flag
   rcl  rcx, 33                 //Mov carry to 32th bit as 0bit of second uint
   add  rax, rax                //Copy 60th bit to carry flag
   adc  qword ptr [@bit_counter + 60 * 8], rcx  //Add rcx to 61th and 60th counters
//...
这相当快:

void count(uint_fast64_t bits){
    uint_fast64_t i64=ffs64(bits);
    while(i64){
        bit_counter[i64-1]++;
        bits=bits & 0xFFFFFFFFFFFFFFFF << i64;
        i64=ffs64(bits);
    }
}

速度根据64位字中
1
位的数量而变化。

显然,这可以通过“垂直计数器”快速完成。从()开始,通过:

考虑一个普通的整数数组,在这里我们读取位 横向:

       msb<-->lsb
  x[0]  00000010  = 2
  x[1]  00000001  = 1
  x[2]  00000101  = 5
对于这样存储的数字,我们可以使用位运算 一次增加它们的任何子集

我们创建一个位图,在对应于 要递增的计数器,并从LSB向上循环通过数组, 在进行时更新位。一次加法的“进位”变为 数组的下一个元素的输入

  input  sum

--------------------------------------------------------------------------------
   A B   C S
   0 0   0 0
   0 1   0 1      sum    = a ^ b
   1 0   0 1      carry  = a & b
   1 1   1 1

  carry = input;
  long *p = buffer;
  while (carry) {
    a = *p; b = carry;
    *p++ = a ^ b;
    carry = a & b;
  }
对于64位字,循环将平均运行6-7次——迭代次数由l决定
       msb<-->lsb
  x[0]  00000010  = 2
  x[1]  00000001  = 1
  x[2]  00000101  = 5
  x[0]  00000110   lsb ↑
  x[1]  00000001       |
  x[2]  00000100       |
  x[3]  00000000       |
  x[4]  00000000   msb ↓
             512
  input  sum

--------------------------------------------------------------------------------
   A B   C S
   0 0   0 0
   0 1   0 1      sum    = a ^ b
   1 0   0 1      carry  = a & b
   1 1   1 1

  carry = input;
  long *p = buffer;
  while (carry) {
    a = *p; b = carry;
    *p++ = a ^ b;
    carry = a & b;
  }