如何在Sandy Bridge的一系列整数中快速将位数计入单独的箱中?

Łuk*_*Lew 14 c++ x86 assembly simd avx

更新:请阅读代码,它不是关于计算一个int中的位

是否有可能通过一些聪明的汇编程序来提高以下代码的性能?

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;
}
Run Code Online (Sandbox Code Playgroud)

Count 是在我算法的最内层循环中.

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

bits 变量几乎是随机位(接近半零和一半)

Pau*_*l R 8

您可以尝试使用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
}
Run Code Online (Sandbox Code Playgroud)

它是如何工作的:基本上我们在这里所做的就是对原始循环进行矢量化,这样我们每循环迭代而不是1处理4位.所以我们现在有16次循环迭代而不是64次.对于每次迭代,我们加载4位bits,然后使用它们作为LUT的索引,包含当前4位的4个增量的所有可能组合.然后,我们将这4个增量添加到bit_counter的当前4个元素中.

加载和存储和添加的数量减少了4倍,但这将由LUT负载和其他内务处理稍微抵消.你可能仍然看到加速2倍.如果您决定尝试,我有兴趣知道结果.

  • 哇。我真的看不到这是怎么回事。但这看起来确实很有希望。 (2认同)

har*_*old 7

也许你可以一次做8个,将8位间隔8个并保持8个uint64的计数.但是,每个单独的计数器只有1个字节,因此count在你必须解压缩那些uint64之前,你只能累积255个调用.


seh*_*ehe 5

看看位摆弄黑客

编辑至于“位位置桶积累”(bit_counter[]),我有一种感觉,这可能是 valarrays + 掩码的一个好例子。不过,这需要相当多的编码+测试+分析。如果您真的感兴趣,请告诉我。

如今,您可以使用绑定元组(TR1、boost 或 C++11)非常接近 valarray 行为;我有一种感觉,它会变得更容易阅读和更慢的编译。

  • 这并不能以任何方式回答我的问题。我也没有看到 valarrays 在这里有帮助。 (9认同)
  • 尽管这些技巧确实非常有用,但我认为它们并不能真正帮助解决这个特定问题。 (5认同)