转换位数组以更快地设置

rxu*_*rxu 2 c++ sse bit-manipulation set bitarray

输入是存储在连续存储器中的比特阵列,每1比特存储器具有1比特的比特阵列.

输出是比特阵列的设定位索引的数组.

例:

bitarray: 0000 1111 0101 1010
setA: {4,5,6,7,9,11,12,14}
setB: {2,4,5,7,9,10,11,12}
Run Code Online (Sandbox Code Playgroud)

获得A组或B组都可以.该集存储为uint32_t数组,因此该集的每个元素都是数组中的无符号32位整数.

如何在单个cpu核心上快5倍左右?

当前代码:

#include <iostream>
#include <vector>
#include <time.h>

using namespace std;

template <typename T>
uint32_t bitarray2set(T& v, uint32_t * ptr_set){
    uint32_t i;
    uint32_t base = 0;
    uint32_t * ptr_set_new = ptr_set;
    uint32_t size = v.capacity();
    for(i = 0; i < size; i++){
        find_set_bit(v[i], ptr_set_new, base);
        base += 8*sizeof(uint32_t);
    }
    return (ptr_set_new - ptr_set);
}

inline void find_set_bit(uint32_t n, uint32_t*& ptr_set, uint32_t base){
    // Find the set bits in a uint32_t
    int k = base;
    while(n){
        if (n & 1){
            *(ptr_set) = k;
            ptr_set++;
        }
        n = n >> 1;
        k++;
    }
}

template <typename T>
void rand_vector(T& v){
    srand(time(NULL));
    int i;
    int size = v.capacity();
    for (i=0;i<size;i++){
        v[i] = rand();
    }
}

template <typename T>
void print_vector(T& v, int size_in = 0){
    int i;

    int size;
    if (size_in == 0){
        size = v.capacity();
    } else {
        size = size_in;
    }
    for (i=0;i<size;i++){
        cout << v[i] << ' ';
    }
    cout << endl;
}

int main(void){
    const int test_size = 6000;
    vector<uint32_t> vec(test_size);
    vector<uint32_t> set(test_size*sizeof(uint32_t)*8);
    rand_vector(vec);
    //for (int i; i < 64; i++) vec[i] = -1;
    //cout << "input" << endl;
    print_vector(vec);
    //cout << "calculate result" << endl;

    int i;
    int rep = 10000;
    uint32_t res_size;

    struct timespec tp_start, tp_end;
    clock_gettime(CLOCK_MONOTONIC, &tp_start);
    for (i=0;i<rep;i++){
        res_size = bitarray2set(vec, set.data());
    }
    clock_gettime(CLOCK_MONOTONIC, &tp_end);
    double timing;
    const double nano = 0.000000001;

    timing = ((double)(tp_end.tv_sec  - tp_start.tv_sec )
           + (tp_end.tv_nsec - tp_start.tv_nsec) * nano) /(rep);

    cout << "timing per cycle: " << timing << endl;
    cout << "print result" << endl;
    //print_vector(set, res_size);
}
Run Code Online (Sandbox Code Playgroud)

结果(用icc -O3 code.cpp -lrt编译)

...
timing per cycle: 0.000739613 (7.4E-4).
print result
Run Code Online (Sandbox Code Playgroud)

0.0008秒将768000位转换为设置.但是每个周期至少有10,000个768,000位的数组.这是每个周期8秒.那很慢.

cpu有popcnt指令和sse4.2指令集.

谢谢.

更新


template <typename T>
uint32_t bitarray2set(T& v, uint32_t * ptr_set){
    uint32_t i;
    uint32_t base = 0;
    uint32_t * ptr_set_new = ptr_set;
    uint32_t size = v.capacity();
    uint32_t * ptr_v;
    uint32_t * ptr_v_end = &(v[size]);
    for(ptr_v = v.data(); ptr_v < ptr_v_end; ++ptr_v){
        while(*ptr_v) {
           *ptr_set_new++ = base + __builtin_ctz(*ptr_v);
           (*ptr_v) &= (*ptr_v) - 1;  // zeros the lowest 1-bit in n
        }
        base += 8*sizeof(uint32_t);
    }
    return (ptr_set_new - ptr_set);
}
Run Code Online (Sandbox Code Playgroud)

此更新版本使用rhashimoto提供的内部循环.我不知道内联是否实际上使函数变慢(我从未想过会发生这种情况!).新的时间是1.14E-5(由icc -O3 code.cpp -lrt随机向量编译和基准).

警告:

我刚刚发现保留而不是调整std :: vector的大小,然后通过原始指向直接写入向量的数据是一个坏主意.首先调整大小然后使用原始指针是好的.在没有初始化数据的情况下,请参阅Robᵩ在重新调整C++ std :: vector <char>时的答案我将仅使用resize而不是reserve并通过调用向量的每个元素的构造函数来停止担心调整大小浪费的时间...至少矢量实际上使用连续的内存,就像一个普通的数组(std :: vector元素是否保证是连续的?)

rha*_*oto 6

我注意到.capacity()你可能想要使用时使用.size().这可能会让你做额外的不必要的工作,并给你错误的答案.

你的循环find_set_bit()遍历单词中的所有32位.您可以只在每个设置位上进行迭代,并使用BSF指令确定最低位的索引.GCC具有__builtin_ctz()生成BSF或等效的内在函数- 我认为英特尔编译器也支持它(如果没有,你可以内联汇编).修改后的函数如下所示:

inline void find_set_bit(uint32_t n, uint32_t*& ptr_set, uint32_t base){
    // Find the set bits in a uint32_t
    while(n) {
       *ptr_set++ = base + __builtin_ctz(n);
       n &= n - 1;  // zeros the lowest 1-bit in n
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的Linux机器上,使用g++ -O3,替换该函数进行编译会将报告的时间从0.000531434降低到0.000101352.

这个问题的答案中有很多方法可以找到一点索引.不过,我认为这__builtin_ctz()对你来说是最好的选择.我不相信你的问题有一个合理的SIMD方法,因为每个输入字产生可变数量的输出.


归档时间:

查看次数:

196 次

最近记录:

9 年,4 月 前