使用 __builtin_popcount 或其他内在函数来处理 _mm256_movemask_pd 比较位图的结果?

Posted

技术标签:

【中文标题】使用 __builtin_popcount 或其他内在函数来处理 _mm256_movemask_pd 比较位图的结果?【英文标题】:Using __builtin_popcount or other intrinsics to process the result of a _mm256_movemask_pd compare bitmap? 【发布时间】:2018-10-08 11:03:00 【问题描述】:

我有这段代码,我想最终实现本文中的位掩码评估算法的修改版本 - Adapting Tree Structures for Processing with SIMD Instructions

#include <stdint.h>
#include <immintrin.h>
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdalign.h>

int main(void)

    __m256d avx_creg, res, avx_sreg;
    int bitmask;
    uint64_t key = 503;

    avx_sreg = _mm256_castsi256_pd(_mm256_set1_epi64x(key));
    alignas(32) uint64_t v[4]; 
    _mm256_store_pd((double*)v, avx_sreg);
    printf("v2_u64: %lld %lld %lld %lld\n", v[0], v[1],v[2],v[3]);
    uint64_t b[4]= 500,505,510,515;
    avx_creg = _mm256_castsi256_pd(
                   _mm256_loadu_si256((__m256i const *)&b));
    //
    alignas(32) uint64_t v1[4]; 
    _mm256_store_pd((double*)v1, avx_creg);
    printf("v2_u64: %lld %lld %lld %lld\n", v1[0], v1[1],v1[2],v1[3]);

    res      = _mm256_cmp_pd(avx_sreg, avx_creg, 30);
    bitmask  = _mm256_movemask_pd(res);
    int mmask = __builtin_popcount(bitmask);
    printf("mmask is %d\n",mmask);

    return 0;

上面的代码将mmask 的值打印为1。所以这里是我完全不清楚的地方。我应该将数字“1”解释为数组索引,其中数组元素大于输入键,还是指设置的位数?

例如,如果我将密钥更改为 499,则 mmask 将打印为 0。

最后,如果我将密钥更改为 517,则 mmask 的值为 4。

有人可以澄清一下吗?我还有第二个问题,如果有人建议,我可以将其作为一个单独的问题提出。是否可以从 AVX 内在函数中获取所有大于给定输入键的值?

【问题讨论】:

与您的问题无关,但简单的main() 不是main 函数的有效声明或定义。参见例如this main function reference 了解更多详情。 @Someprogrammerdude - 感谢您的澄清。它使用我的 gcc 版本(即 7.3)可以很好地编译,而且我是 C 初学者;-)。所以请随时纠正。 prog.c:8:1: warning: type specifier missing, defaults to 'int' [-Wimplicit-int] 不,它编译不好;) @hellow - 看起来我们的编辑交叉了:)。 那么这不是真正的掩码,而是bitmask中有多少个1的计数 【参考方案1】:

movemask 通过从向量中获取每个元素的高位来生成一个整数位图。将其打印为 hex 或 base-2 以便更好地查看。

如果您只关心 0 与非零计数,只需检查 if(bitmask != 0)

if(bitmask == 0x0f) 检查它们是否都是真的。 (4 位为 4 元素向量)。


使用 popcount 找出有多少是真的。 __builtin_popcnt 计算其输入中设置的位数。

使用__builtin_ctz 查找比较结果为真的第一个元素的位置。 (如果向量是从内存中加载的,则从低内存地址到高内存地址计数)。请注意,__builtin_ctz 仅对非零输入有意义。例如在memchr 循环中,只有在跳出_mm256_movemask_epi8(cmp_result) == 0 上的搜索循环后才能使用ctz,以确定此向量中存在匹配项。 (epi8 因为我在谈论一个字节搜索循环,不像你打包的-double 比较)。

如果您已经需要 AVX2,您可能希望使用 BMI1 _lzcnt_u32(bitmask) 在位掩码 = 0 上获得明确定义的结果(32 个前导零)。 (因为我认为所有的 AVX2 CPU 都有 BMI1。)


要遍历匹配项,您可以使用 clear-lowest-set-bit 操作,如果仍有任何位设置,则 ctz 找出哪一个。见Clearing the lowest set bit of a number。

x &amp; (x-1) 将有效地编译为 BMI1 blsr 指令,如果您在启用 BMI1 的情况下进行编译,例如-march=haswell

(为了使其正常工作,您肯定需要一个与您的向量元素大小相匹配的 movemask,因此对于 64 位整数,请将您的向量转换为 _pd,以便您可以使用 _mm256_movemask_pd。)

【讨论】:

假设我有一个 k-ary 树,我位于第 (k-1) 个节点,并且该节点有四个子节点,每个子节点由 64 位整数标识。然后我想将我的搜索关键字与这四个叶节点进行比较。我发现只有第三个叶子节点大于搜索键。我应该调用什么方法来确定哪个叶节点大于搜索键?我假设搜索键是一个寄存器,叶节点在另一个寄存器中,就像上面的例子一样 @gansub: 引用我的回答:使用__builtin_ctz 查找第一个比较为真的元素的位置。 将其用于@987654343 的结果@/_mm256_movemask_pd。它给你一个整数索引。 (或 movemask epi8 并将索引右移 3...) 最后一个问题。假设叶节点 3 前导节点 4 都大于搜索关键字。 __builtin_ctz 也是解决该问题的方法吗?我可以通过该返回值“迭代”以获取所有更大的节点吗? @gansub:是的,您可以使用 clear-lowest-set-bit 操作,如果仍然设置了任何位,则 ctz 找出哪个位。 Clearing the lowest set bit of a number。 (x &amp; (x-1) 将有效地编译为 BMI1 blsr 指令,如果您在启用 BMI1 的情况下进行编译,例如使用 -march=haswell。) @gansub:该代码使用了_ps,因此每个向量的元素数量是原来的两倍,因此设置位的数量也是全真的两倍。

以上是关于使用 __builtin_popcount 或其他内在函数来处理 _mm256_movemask_pd 比较位图的结果?的主要内容,如果未能解决你的问题,请参考以下文章

std::bitset<N>::count vs __builtin_popcount

std::bitset<N>::count vs __builtin_popcount

一些小技巧

四种GCC内置位运算函数

竞赛常用STL备忘录

gcc inline asm 不编译