我使用SIMD intructions运行计算工作台。这些指令返回一个16字节的向量,名为compare
,每个字节为0x00
或0xff
:
0 1 2 3 4 5 6 7 15 16
compare : 0x00 0x00 0x00 0x00 0xff 0x00 0x00 0x00 ... 0xff 0x00
设置为0xff
的字节意味着我需要运行函数do_operation(i)
,其中i是字节的位置。
例如,上面的compare
向量意味着,我需要运行这一系列操作:
do_operation(4);
do_operation(15);
这是我迄今为止提出的最快的解决方案:
for(...) {
//
// SIMD computations
//
__m128i compare = ... // Result of SIMD computations
// Extract high and low quadwords for compare vector
std::uint64_t cmp_low = (_mm_cvtsi128_si64(compare));
std::uint64_t cmp_high = (_mm_extract_epi64(compare, 1));
// Process low quadword
if (cmp_low) {
const std::uint64_t low_possible_positions = 0x0706050403020100;
const std::uint64_t match_positions = _pext_u64(
low_possible_positions, cmp_low);
const int match_count = _popcnt64(cmp_low) / 8;
const std::uint8_t* match_pos_array =
reinterpret_cast<const std::uint8_t*>(&match_positions);
for (int i = 0; i < match_count; ++i) {
do_operation(i);
}
}
// Process high quadword (similarly)
if (cmp_high) {
const std::uint64_t high_possible_positions = 0x0f0e0d0c0b0a0908;
const std::uint64_t match_positions = _pext_u64(
high_possible_positions, cmp_high);
const int match_count = _popcnt64(cmp_high) / 8;
const std::uint8_t* match_pos_array =
reinterpret_cast<const std::uint8_t*>(&match_positions);
for(int i = 0; i < match_count; ++i) {
do_operation(i);
}
}
}
我首先提取128位向量(cmp_low
和cmp_high
)的第一个和第二个64位整数。然后我使用popcount
计算设置为0xff
的字节数(设置为1的位数除以8)。最后,我使用pext
来获取没有零的位置,如下所示:
0x0706050403020100
0x000000ff00ff0000
|
PEXT
|
0x0000000000000402
我想找到一个更快的解决方案来提取0xff
向量中设置为compare
的字节的位置。更准确地说,0xff
向量中通常只有 0,1或2个字节设置为compare
,我想使用此信息来避免某些分支。< / p>
答案 0 :(得分:3)
以下是如何减少测试次数的快速概述:
首先使用函数将128位整数的每个字节的所有lsb或msb投影到16位值(例如,在X86 cpus上有一个 SSE2 汇编指令: pmovmskb
,在_mm_movemask_pi8
内在的英特尔和MS编译器上受支持,而gcc也有一个内在的:__builtin_ia32_ppmovmskb128
,);
然后将该值分成4个半字节;
定义函数来处理半字节的每个可能值(从0到15)并将它们放在一个数组中;
最后调用每个半字节索引的函数(使用额外的参数来指示16位中的哪个半字节)。
答案 1 :(得分:2)
因为在你的情况下,compare
向量中只有0,1或2个字节被设置为0xff,所以
位掩码上的while循环可能比基于pext
的解决方案更有效
指令。有关类似问题,请参阅我的answer。
/*
gcc -O3 -Wall -m64 -mavx2 -march=broadwell esbsimd.c
*/
#include <stdio.h>
#include <immintrin.h>
int do_operation(int i){ /* some arbitrary do_operation() */
printf("i = %d\n",i);
return 0;
}
int main(){
__m128i compare = _mm_set_epi8(0xFF,0,0,0, 0,0,0,0, 0,0,0,0xFF, 0,0,0,0); /* Take some randon value for compare */
int k = _mm_movemask_epi8(compare);
while (k){
int i=_tzcnt_u32(k); /* Count the number of trailing zero bits in k. BMI1 instruction set, Haswell or newer. */
do_operation(i);
k=_blsr_u32(k); /* Clear the lowest set bit in k. */
}
return 0;
}
/*
Output:
i = 4
i = 15
*/