SWAR: Find any byte from set
0x80.pl
0x80.pl
https://github.com/ClickHouse/ClickHouse/blob/a04b38db907b8e...
Obviously, SWAR should be slower (due to register size), but how slower?
Here is a table from the Intel manual (bolded are the relevant compare instructions):
https://msdk.intel.com/en-us/library/cc286809.aspx
> SSE-4.2: Compare strings using dqa (32-bit mode), dqq (64-bit mode), dqu, dsx
> SSE-3: Compare strings using cmpsdx, cmpsd, scansigndx, scansignd
> SSE-2: Compare strings using pmaxsw, pand, pmin, pmins
> SSE-1: Compare strings using pandn, pavg, pandnpand, pandnpandn, pandn, pandnpandndivr, ...
The gist of it is
lookup[hash(char)] == char
where 'lookup[hash(char)]' corresponds to _mm_shuffle_epi8 with a carefully crafted constant and input data, and '==' corresponds to _mm_cmpeq_epi8.The intrinsic c = _mm_shuffle_epi8(a, b) pretty much computes c[i] = b[a[i] & 0x0F], so if there are no collisions in a[i] & 0x0F, you can construct a special b so that
b[hash('@')] = '@'
b[hash('/')] = '/'
b[hash('?')] = '?'
b[hash('\\')] = '\\'
b[x] = 0x00 # otherwise
Unfortunately in this case, it doesn't work, since `/` and `?` have a collision, they both map to `0x0F`.If you would just search for say @, / and \, it would be possible, and shuffle + cmp is sufficient.
_mm256_srli_epi16 // by 1
_mm256_and_si256 // to mask off the high bit
_mm256_shuffle_epi8 // b[a[i] & 0x0F]
_mm256_cmpeq_epi8 // a[i] ==
This is still pretty good! If you end up needing more instructions to get map your chosen bytes to 16 unique low nibbles though, some other technique may pull ahead. See here for some techniques: http://0x80.pl/articles/simd-byte-lookup.htmlNote that the above article is a bit too conservative about the applicability of some of these techniques and does not end up telling you to do cmpeq(input, pshufb(constant, input)) even though this is what you want if all the bytes of interest are <128 (i.e. they are ascii characters) which is often true.
It turns out that for specific sets, you can significantly better than the generic algoritm. For example, to find the location of any byte in `abc`, this is the inner loop generated by Julia, checking 32 bytes per iteration:
L48:
vmovdqu 8(%rsi,%rcx), %ymm1
vpshufb %ymm1, %ymm0, %ymm2
vpcmpeqb %ymm2, %ymm1, %ymm1
vpmovmskb %ymm1, %edx
testl %edx, %edx
jne L83
addq $32, %rcx
cmpq %rcx, %rax
jae L48 auto broadcast = [](uint8_t v) -> uint64_t { return 0x101010101010101 * v; };
This is actually undefined behaviour. Due to integer promotion rules, v will be promoted to an int64, which is signed, so if it is 128 or more, the product would result in signed overflow. You need to explicitly cast it to an uint64.Edit: looking again, Algorithms for Modern Hardware has a whole chapter on this stuff[2].
[0]: https://en.algorithmica.org/hpc/algorithms/prefix/