SWAR explained: parsing eight digits
https://lemire.me/blog/2022/01/21/swar-explained-parsing-eight-digits/
整个变换过程如下
expr | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
X | b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7 |
Y = (X + (10X >>8)) & mask0 | b0 + 10b1 | 0 | 10b3 + b2 | 0 | 10b5+b4 | 0 | 10b7+b6 | 0 |
Z = (Y + (100*Y>>16)) & mask1 | 1000b3+100b2+10b1+b0 | X | 0 | 0 | 1000b7+100b6+10b5+b4 | X | 0 | 0 |
T = (Z + (10000*Z) >> 32) & mask2 | (1000b7+100b6+10b5+b4)*10000 + 1000b3+100b2+10b1+b0 | X | X | X | 0 | 0 | 0 | 0 |
uint32_t toint(const char* digits) { uint64_t X; memcpy(&X, digits, 8); X -= 0x3030303030303030; uint64_t mask0 = 0x00ff00ff00ff00ff; uint64_t Y = ((((10 << 8) + 1) * X) >> 8) & mask0; uint64_t mask1 = 0x0000ffff0000ffff; uint64_t Z = ((((100 << 16) + 1) * Y) >> 16) & mask1; uint64_t T = ((((10000UL << 32) + 1) * Z) >> 32); return (uint32_t)T; }
文章里面的变换可以这里的差异是,在第二步的时候是0-4, 2-6这样配对,而不是0-2, 4-6这样配对。看上去好像效率会更好些,因为不存在数据依赖。
另外一篇文章 https://lemire.me/blog/2022/04/28/removing-characters-from-strings-faster-with-avx-512/ AVX512里面有个指令很有意思 `_mm512_mask_compressstoreu_epi8`
size := 8 m := base_addr FOR j := 0 to 63 i := j*8 IF k[j] MEM[m+size-1:m] := a[i+7:i] m := m + size FI ENDFOR
可以根据mask进行memcpy, 这个好像在现实中蛮有用的。
接着作者又跟进了一篇 https://lemire.me/blog/2022/05/10/faster-bitset-decoding-using-intel-avx-512/, 里面有个指令也差不多,只不过没有做store memory而是到reg上 `_mm512_maskz_compress_epi8`. 这个好处是,存在reg上还可以做额外的操作,而放在memory上之后做操作还需要重新load上来。
size := 8 m := 0 FOR j := 0 to 63 i := j*8 IF k[j] dst[m+size-1:m] := a[i+7:i] m := m + size FI ENDFOR dst[511:m] := 0 dst[MAX:512] := 0