He usado SSE2/AVX2 para ayudar a realizar una búsqueda de árbol
B +. Aquí hay un código para realizar una búsqueda binaria en una
línea de caché completa de 16 DWORD en AVX2:
// perf-critical: ensure this is 64-byte aligned. (a full cache line)
union bnode
{
int32_t i32[16];
__m256i m256[2];
};
// returns from 0 (if value < i32[0]) to 16 (if value >= i32[15])
unsigned bsearch_avx2(bnode const* const node, __m256i const value)
{
__m256i const perm_mask = _mm256_set_epi32(7, 6, 3, 2, 5, 4, 1, 0);
//compare the two halves of the cache line.
__m256i cmp1 = _mm256_load_si256(&node->m256[0]);
__m256i cmp2 = _mm256_load_si256(&node->m256[1]);
cmp1 = _mm256_cmpgt_epi32(cmp1, value);//PCMPGTD
cmp2 = _mm256_cmpgt_epi32(cmp2, value);//PCMPGTD
//merge the comparisons back together.
//
//a permute is required to get the pack results back into order
//because AVX-256 introduced that unfortunate two-lane interleave.
//
//alternately, you could pre-process your data to remove the need
//for the permute.
__m256i cmp = _mm256_packs_epi32(cmp1, cmp2);//PACKSSDW
cmp = _mm256_permutevar8x32_epi32(cmp, perm_mask);//PERMD
//finally create a move mask and count trailing
//zeroes to get an index to the next node.
unsigned mask = _mm256_movemask_epi8(cmp);//PMOVMSKB
return _tzcnt_u32(mask)/2;//TZCNT
}
Usted terminará con una única rama altamente predecible por
bnode
, para probar si se ha alcanzado el final del
árbol.
Esto debería ser trivialmente escalable para AVX-512.
Para preprocesar y deshacerse de esa instrucción lenta de
PERMD
, esto se usaría:
void preprocess_avx2(bnode* const node)
{
__m256i const perm_mask = _mm256_set_epi32(3, 2, 1, 0, 7, 6, 5, 4);
__m256i *const middle = (__m256i*)&node->i32[4];
__m256i x = _mm256_loadu_si256(middle);
x = _mm256_permutevar8x32_epi32(x, perm_mask);
_mm256_storeu_si256(middle, x);
}