使用SIMD/AVX/SSE进行树遍历

use*_*743 6 performance assembly simd avx micro-optimization

我目前正在研究是否有可能加速面包车Emde Boas(或任何树)遍历.给定单个搜索查询作为输入,已经在缓存行中具有多个树节点(van emde Boas布局),树遍历似乎是指令瓶颈.

作为SIMD/AVX/SSE指令的新手,我想知道该主题的专家是否可以将多个节点一次比较到一个值,然后找出要进一步遵循的树路径.我的研究得出以下问题:

在构建SIMD/AVX/SSE寄存器等时浪费了多少cpu周期/指令.如果构建比手动遍历整个子树需要更多的时间(2 + 4 + 8个节点),这将使其用于wayne. 1个大小为64字节的高速缓存行).

在寻找合适的SIMD/AVX/SSE寄存器时,浪费了多少cpu周期/指令,并确定了要遵循的路径?任何人都可以想出一个聪明的方法,以便那些"findMinimumInteger"AVX指令可用于决定1(??)cpu周期?

你猜怎么着?

加速树遍历的另一种更棘手的方法是在最后一个树级别中很可能在节点中紧密地着陆时,同时运行多个搜索查询.对此有何猜测?但是,它必须将那些不再属于同一子树的查询放在一边,然后在完成树的第一次"并行遍历"之后递归地找到它们.树查询具有顺序但不是恒定的访问模式(query [i]总是<而不是查询[i + 1]).

重要的是:这个东西是关于整数树的,这就是使用van Emde Boas Tree的原因(也许x-fast/y-fast稍后尝试)

我很好奇你在这个问题上的50美分是多少,因为人们可能会对大型树的最高性能感兴趣.提前感谢你花时间在这上面:-)

Cor*_*son 10

我使用SSE2/AVX2来帮助执行B +树搜索.这是在AVX2中对16个DWORD的完整缓存行执行二进制搜索的代码:

// 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
}
Run Code Online (Sandbox Code Playgroud)

您最终会得到一个高度可预测的分支bnode,以测试是否已到达树的末尾.

这应该可以轻松扩展到AVX-512.

要预处理并删除那个慢速PERMD指令,可以使用:

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 我现在正在工作,所以无法查找代码。SIMD 基本上是一种对固定数量的整数执行二分搜索并减少这些分支的快速方法。这就是它的全部作用。 (3认同)
  • 您的 B 树节点适合单个缓存行。我无法想象 SSE(等)会提供很大的性能优势,即使 B 树完全适合缓存(这似乎是一个相当简单的情况)。我已经在汇编器中构建了内存中的 B 树,它们具有相同的约束;几乎每个节点只能得到一个真正的“单分支”,因为分支预测器几乎正确。在最坏的情况下,你可以对节点中的键进行二分查找;平均数只有6。可以用上交所报价,不带数字进行比较吗? (2认同)

use*_*743 6

根据您的代码,我继续进行基准测试3个选项:AVX2驱动,嵌套分支(4个跳转)和无分支变体.这些是结果:

//性能表... //全部使用缓存行大小64byteAligned块(van Emde-Boas布局); 每个缓存行展开循环; //所有优化都已开启.每个元素为4个字节.Intel i7 4770k Haswell @ 3.50GHz

Type        ElementAmount       LoopCount       Avg. Cycles / Query
===================================================================
AVX2        210485750           100000000       610 cycles    
AVX2        21048575            100000000       427 cycles           
AVX2        2104857             100000000       288 cycles 
AVX2        210485              100000000       157 cycles   
AVX2        21048               100000000       95 cycles  
AVX2        2104                100000000       49 cycles    
AVX2        210                 100000000       17 cycles 
AVX2        100                 100000000       16 cycles   


Type        ElementAmount       LoopCount       Avg. Cycles / Query
===================================================================  
Branching   210485750           100000000       819 cycles 
Branching   21048575            100000000       594 cycles 
Branching   2104857             100000000       358 cycles 
Branching   210485              100000000       165 cycles 
Branching   21048               100000000       82 cycles
Branching   2104                100000000       49 cycles 
Branching   210                 100000000       21 cycles 
Branching   100                 100000000       16 cycles   


Type        ElementAmount       LoopCount       Avg. Cycles / Query
=================================================================== 
BranchLESS  210485750           100000000       675 cycles 
BranchLESS  21048575            100000000       602 cycles 
BranchLESS  2104857             100000000       417 cycles
BranchLESS  210485              100000000       273 cycles 
BranchLESS  21048               100000000       130 cycles 
BranchLESS  2104                100000000       72 cycles 
BranchLESS  210                 100000000       27 cycles 
BranchLESS  100                 100000000       18 cycles
Run Code Online (Sandbox Code Playgroud)

所以我的结论如下:当内存访问是最佳的时候,AVX可以帮助Tree的大于200k元素.在此之下几乎没有任何惩罚(如果你不使用AVX的话).值得对此进行基准测试之夜.感谢所有参与者:-)