我对此没有任何技巧,但我确实有一个 SIMD 技巧。
先说几个观察,
- 将 0 解释为 -1,这个问题就变成了“找到第一个
i,以便第一位i总和为 0”。
- 0 是偶数,但在此解释下所有位都具有奇数,这给出了
i必须是偶数的见解,并且可以通过 2 位块来分析此问题。
- 01 和 10 不会改变平衡。
将 2 组分散到字节后(以下均未测试),
// optionally use AVX2 _mm_srlv_epi32 instead of ugly variable set
__m128i spread = _mm_shuffle_epi8(_mm_setr_epi32(x, x >> 2, x >> 4, x >> 6),
_mm_setr_epi8(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15));
spread = _mm_and_si128(spread, _mm_set1_epi8(3));
将 00 替换为 -1,将 11 替换为 1,将 01 和 10 替换为 0:
__m128i r = _mm_shuffle_epi8(_mm_setr_epi8(-1, 0, 0, 1, 0,0,0,0,0,0,0,0,0,0,0,0),
spread);
计算前缀总和:
__m128i pfs = _mm_add_epi8(r, _mm_bsrli_si128(r, 1));
pfs = _mm_add_epi8(pfs, _mm_bsrli_si128(pfs, 2));
pfs = _mm_add_epi8(pfs, _mm_bsrli_si128(pfs, 4));
pfs = _mm_add_epi8(pfs, _mm_bsrli_si128(pfs, 8));
找到最高的 0:
__m128i iszero = _mm_cmpeq_epi8(pfs, _mm_setzero_si128());
return __builtin_clz(_mm_movemask_epi8(iszero) << 15) * 2;
出现<< 15and*2是因为生成的掩码是 16 位,但 clz 是 32 位,因为如果顶部字节为零,则表示采用 1 组 2,而不是零。