如何让clang/gcc对循环数组比较进行矢量化?

hgqdbh6s  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(144)
bool equal(uint8_t * b1,uint8_t * b2){
    b1=(uint8_t*)__builtin_assume_aligned(b1,64);
    b2=(uint8_t*)__builtin_assume_aligned(b2,64);
    for(int ii = 0; ii < 64; ++ii){
        if(b1[ii]!=b2[ii]){
            return false;
        }
    }
    return true;
}

看看这个汇编,clang和gcc除了循环展开之外似乎没有添加任何优化(使用标志-O3 -mavx 512 f-msse4.2)。我认为把这两个内存区域放在512位寄存器中并比较它们是很容易的。更令人惊讶的是,两个编译器也未能优化这一点(理想情况下只需要一个64位比较,不需要特殊的大型寄存器):

bool equal(uint8_t * b1,uint8_t * b2){
    b1=(uint8_t*)__builtin_assume_aligned(b1,8);
    b2=(uint8_t*)__builtin_assume_aligned(b2,8);
    for(int ii = 0; ii < 8; ++ii){
        if(b1[ii]!=b2[ii]){
            return false;
        }
    }
    return true;
}

那么,这两个编译器都是愚蠢的,还是有什么原因导致代码没有进行矢量化?有没有什么方法可以在编写内联汇编之前强制进行矢量化?

ddhy6vgd

ddhy6vgd1#

“我认为”以下是最有效的

memcmp(b1, b2, any_size_you_need);

尤其是对于大阵!
(For小数组,反正也没有太多收获!)
否则,您将需要使用英特尔Intrinsics手动矢量化。(chtz也提到过。)我开始研究这个问题,直到我想到了memcmp

igsr9ssn

igsr9ssn2#

编译器必须假设一旦函数返回(或退出循环),它就不能读取当前索引后面的任何字节--例如,如果其中一个指针碰巧指向了无效内存边界附近的某个地方。您可以给予编译器一个机会来优化这一点,方法是使用(非懒惰的)按位&/|运算符来合并各个比较的结果:

bool equal(uint8_t * b1,uint8_t * b2){
    b1=(uint8_t*)__builtin_assume_aligned(b1,64);
    b2=(uint8_t*)__builtin_assume_aligned(b2,64);
    bool ret = true;
    for(int ii = 0; ii < 64; ++ii){
        ret &= (b1[ii]==b2[ii]);
    }
    return ret;
}

Godbolt演示:https://godbolt.org/z/3ePh7q5rM
但是gcc仍然无法矢量化这个函数,所以如果这个函数对性能很关键,你可能需要编写这个函数的手动矢量化版本。

相关问题