有效地收集单个字节,以4

时间:2015-08-12 23:49:04

标签: c intrinsics avx

我正在尝试优化一种算法,该算法将处理大量数据集,这些数据集可能会受益于AVX SIMD指令。不幸的是,输入存储器布局对于所需的计算并不是最佳的。必须重新排序信息,方法是将恰好4个字节的单个字节中的__m256i值组合起来:

开始编辑

我的目标CPUS不支持AVX2指令,所以像@Elalfer和@PeterCordes指出的那样,我无法使用__m256i值,代码必须转换为使用__m128i值代替)

结束编辑

内存中的DataSet布局

Byte 0   | Byte 1   | Byte 2   | Byte 3
Byte 4   | Byte 5   | Byte 6   | Byte 7
...
Byte 120 | Byte 121 | Byte 122 | Byte 123
Byte 124 | Byte 125 | Byte 126 | Byte 127

__m256i变量中的所需值:

| Byte 0 | Byte 4 | Byte 8 |     ...     | Byte 120 | Byte 124 |

除了这个简单的代码之外,是否有更有效的方法来收集和重新排列跨步数据?

union {  __m256i   reg;   uint8_t bytes[32]; } aux;
...
for( int i = 0; i < 32; i++ )
    aux.bytes[i] = data[i * 4];

修改

我正在尝试优化的步骤是一个位列转换;换句话说,某列的位(我的数据排列中的32个可能位列)应该成为单个uint32_t值,而其余位则被忽略。

我通过重新排列数据来执行转置,执行左移以将所需的位列作为每个子字节中的最高有效位,最后提取并将这些位组合成单个uint32通过_mm256_movemask_epi8()内在函数_t值。

3 个答案:

答案 0 :(得分:4)

其中一种方法是 - 用_mm256_shuffle_epi8打包字节,混合所有_mm256_blend_epi32结果向量(你需要做4个这样的加载+随机播放),并做一个32位permute _mm256_permutevar8x32_epi32

这是一个伪代码(我希望你能想出一下shuffle mask):

L1 = load32byte(buf)
L2 = load32byte(buf+32)
L3 = load32byte(buf+64)
L4 = load32byte(buf+96)

// Pack 4 bytes in the corresponding 32bit DWORD in each lane and zero-out other bytes
L1 = shuffle(L1, mask_for_L1)   
L2 = shuffle(L2, mask_for_L2)
L3 = shuffle(L3, mask_for_L3)
L4 = shuffle(L4, mask_for_L4)

// Vec = blend(blend(L1,L2),blend(L3,L4))
Vec = or(or(or(L1,L2),L3),L4)
Vec = permute(Vec)  // fix DWORD order in the vector

更新:忘记我说的原因&#34;将其他字节清零&#34; - 这样您就可以将blend替换为or

更新:通过在下面的彼得评论中重新排列or次操作,减少了一个周期延迟。

<强> PS 即可。我还建议您在进行位操作时查看BMI指令集。

答案 1 :(得分:2)

你可以尝试展开那个循环,这应该至少摆脱循环体中的一个比较(i <32),一个增量(i ++)和一个乘法(i * 4)。恒定阵列偏移也可能比变量稍微快一些。但请注意,无论如何,您的编译器可能会生成相似(或更好)的代码,并启用相应的编译选项。

union {  __m256i   reg;   uint8_t bytes[32]; } aux;
...
aux.bytes[0] = data[0];
aux.bytes[1] = data[3];
...
aux.bytes[31] = data[124];

答案 2 :(得分:2)

我只是注意到编辑,它有一个特殊情况的答案。

如果您需要在同一数据上执行许多不同的位位置,那么您当前的计划是好的。

如果您只需要128B内存中的一位位置(尤其是最高位位置),则可以使用_mm256_movemask_ps从每个32b元素中获取高位。然后在GP寄存器中组合四个8位掩码。

一个好的编译器应该优化它:

vmovdqu   ymm0, [buf + 0]
; to select a different bit:
; vpslld  ymm0, ymm0, count   ; count can be imm8 or the low byte of an xmm register
vmovmskps eax, ymm0

vmovdqu   ymm0, [buf + 32]
vmovmskps ebx, ymm0

...  ecx and edx

mov       ah, bl
mov       ch, dl
shl       ecx, 16
or        eax, ecx

只有在测试高位时才这样很好(所以你不需要在vmovmsk之前移动每个向量)。即便如此,这可能是比其他解决方案更多的指令(​​和代码大小)。

回答原来的问题:

与Elalfer的想法类似,但使用shuffle单位代替pack pshufb指令。此外,所有AND都是独立的,因此它们可以并行执行。 Intel CPU可以同时执行3个AND,但只能进行一次shuffle。 (或者在Haswell之前一次两次洗牌。)

// without AVX2: you won't really be able to
// do anything with a __m256i, only __m128i
// just convert everything to regular _mm_..., and leave out the final permute

mask = _mm256_set1_epi32(0x000000ff);

// same mask for all, and the load can fold into the AND
// You can write the load separately if you like, it'll still fold
L1 = and(mask, (buf))     // load and zero the bytes we don't want
L2 = and(mask, (buf+32))
L3 = and(mask, (buf+64))
L4 = and(mask, (buf+96))

// squish dwords from 2 concatenated regs down to words in 1 reg
pack12 = _mm256_packus_epi32(L1, L2);
pack34 = _mm256_packus_epi32(L3, L4);

packed = _mm256_packus_epi16(pack12, pack34);  // note the different width: zero-padded-16 -> 8

Vec = permute(packed)  // fix DWORD order in the vector (only needed for 256b version)

Vec = shift(Vec, bit_wanted)
bitvec = movemask(Vec)

    // shift:
    //  I guess word or dword granularity is fine, since byte granularity isn't available.
    //  You only care about the high bit, so it doesn't matter than you're not shifting zeroes into the bottom of each byte.

    // _mm_slli_epi32(Vec, imm8): 1 uop, 1c latency if your count is a compile-time constant.
    // _mm_sll_epi32 (Vec, _mm_cvtsi32_si128(count)): 2uop 2c latency if it's variable.

    // *not* _mm_sllv_epi32(): slower: different shift count for each element.

如果您只使用AVX(就像您说的那样),那么您将无法获得256b整数指令。只需构建128b向量,并在掩码数据时获得16b。最后你不需要最后的选择。

使用整数指令合并蒙版:(m2<<16) | m1。如果需要,通过组合两个32b掩模,甚至可以达到64b的掩模数据。

性能:这可以避免使用AVX单独加载指令,因为vpand可以micro-fuse a memory operand if used with a one-register addressing mode

  • 周期1:3 vpand指令。 (或者只有2,如果我们在地址上等待,因为只有2个加载端口。)
  • 周期2:最后一两个vpand,一个pack(L1,L2)
  • 周期3:下一个pack(L3,L4)
  • 周期4:最终pack
  • // 256b AVX2:permute
  • 周期5:使用imm8计数的打包移位:1 uop,1c延迟。
  • 周期6:movemask(3周期延迟)

延迟= 8(SnB及更高版本)

吞吐量:3次shuffle(p5),4次逻辑(p015),1次移位(p0),1 pmovmsk(p0)。 4加载uops。

  • SnB / IvB:9 ALU uops - &gt; 3C。 4个内存读取:2c。
    因此,根据您对掩模的操作,将需要3个累加器来保持执行端口饱和。 (ceil(8/3)= 3。)。

变量中的移位计数通过编译器内联/展开无法解析为编译时常量:latency = 9.并且移位会为p1 / p5生成另一个uop。

对于Haswell及更高版本的AVX2,vpermd还有3个额外延迟。