Clang:将 bool[64] 数组自动矢量化转换为 uint64_t 位掩码

He3*_*xxx 9 c++ clang compiler-optimization avx2 avx512

我想将 a 转换bool[64]为 a uint64_t,其中每个位代表输入数组中元素的值。

在现代 x86 处理器上,这可以非常有效地完成,例如vptestmd与 AVX512 或vpmovmskbAVX256 一起使用。当我将 clang 的 bool 向量扩展与 结合使用时__builtin_convertvector,我对结果感到满意:

uint64_t handvectorized(const bool* input_) noexcept {
    const bool* __restrict input = std::assume_aligned<64>(input_);

    using VecBool64 __attribute__((vector_size(64))) = char;
    using VecBitmaskT __attribute__((ext_vector_type(64))) = bool;

    auto input_vec = *reinterpret_cast<const VecBool64*>(input);
    auto result_vec = __builtin_convertvector(input_vec, VecBitmaskT);    

    return reinterpret_cast<uint64_t&>(result_vec);
}
Run Code Online (Sandbox Code Playgroud)

产生(Godbolt):

vmovdqa64  zmm0, zmmword ptr [rdi]
vptestmb   k0, zmm0, zmm0
kmovq      rax, k0
vzeroupper
ret
Run Code Online (Sandbox Code Playgroud)

但是,我无法让 clang (或 GCC 或 ICX)使用(可移植的)标量代码生成任何使用向量掩码提取的任何内容。对于这个实现:

vmovdqa64  zmm0, zmmword ptr [rdi]
vptestmb   k0, zmm0, zmm0
kmovq      rax, k0
vzeroupper
ret
Run Code Online (Sandbox Code Playgroud)

clang 生成 64*8B = 512B 查找表和 39 条指令。

这个实现以及我尝试过的其他一些标量实现(无分支、逆位顺序、使用)都可以在godboltstd::bitset上找到。它们都不会产生接近手写向量指令的代码。

有什么我遗漏的或者有什么原因导致优化在这里效果不佳吗?我可以编写一个标量版本来生成合理的矢量化代码吗?

我特别想知道,因为“handvectorized”版本不使用任何特定于平台的内在函数,并且实际上没有太多编程。它所做的只是“加载为向量”和“转换为位掩码”。也许 clang 根本没有检测到循环模式?这对我来说只是感觉很奇怪,一个简单的按位 OR 归约循环感觉像是一种常见模式,并且循环向量化器的文档明确列出了使用 OR 作为受支持功能的归约。


编辑:用评论中的建议更新了 godbolt 链接

Edit2:我刚刚意识到这个问题有一个开放的 LLVM 问题:https ://github.com/llvm/llvm-project/issues/41997