截至2023年,让编译器生成良好的向量代码的最佳方法是迭代处理与您的向量寄存器大小相等的块。在AVX2或更高版本中,这些块将为256位或32字节。
#include <omp.h>
#include <stdalign.h>
#include <stddef.h>
#include <stdint.h>
#define ALIGNMENT 16U
size_t countEqualBytes(const size_t n, const uint8_t a[n], const uint8_t b[n]) {
size_t sum = 0;
size_t i = 0;
if (n >= 32U) {
const size_t sentinel = n - 31U;
for (i = 0; i < sentinel; i += 32U) {
sum += (size_t)((a[i] == b[i]) +
(a[i + 1] == b[i + 1]) +
(a[i + 2] == b[i + 2]) +
(a[i + 3] == b[i + 3]) +
(a[i + 4] == b[i + 4]) +
(a[i + 5] == b[i + 5]) +
(a[i + 6] == b[i + 6]) +
(a[i + 7] == b[i + 7]) +
(a[i + 8] == b[i + 8]) +
(a[i + 9] == b[i + 9]) +
(a[i + 10] == b[i + 10]) +
(a[i + 11] == b[i + 11]) +
(a[i + 12] == b[i + 12]) +
(a[i + 13] == b[i + 13]) +
(a[i + 14] == b[i + 14]) +
(a[i + 15] == b[i + 15]) +
(a[i + 16] == b[i + 16]) +
(a[i + 17] == b[i + 17]) +
(a[i + 18] == b[i + 18]) +
(a[i + 19] == b[i + 19]) +
(a[i + 20] == b[i + 20]) +
(a[i + 21] == b[i + 21]) +
(a[i + 22] == b[i + 22]) +
(a[i + 23] == b[i + 23]) +
(a[i + 24] == b[i + 24]) +
(a[i + 25] == b[i + 25]) +
(a[i + 26] == b[i + 26]) +
(a[i + 27] == b[i + 27]) +
(a[i + 28] == b[i + 28]) +
(a[i + 29] == b[i + 29]) +
(a[i + 30] == b[i + 30]) +
(a[i + 31] == b[i + 31]));
}
}
for (; i<n; i++) {
sum += (a[i] != b[i]);
}
return sum;
}
使用Clang 16或ICX 2022与
-std=c17 -O3 -march=x86-64-v4
选项能够将此关键循环编译为:
.LBB0_5: # =>This Inner Loop Header: Depth=1
vmovdqu ymm0, ymmword ptr [rsi + r10]
vmovdqu ymm1, ymmword ptr [rsi + r10 + 32]
vmovdqu ymm2, ymmword ptr [rsi + r10 + 64]
vmovdqu ymm3, ymmword ptr [rsi + r10 + 96]
vpcmpeqb k0, ymm0, ymmword ptr [rdx + r10]
kmovd ebx, k0
popcnt ebx, ebx
add rbx, rax
vpcmpeqb k0, ymm1, ymmword ptr [rdx + r10 + 32]
kmovd eax, k0
popcnt eax, eax
add rax, rbx
vpcmpeqb k0, ymm2, ymmword ptr [rdx + r10 + 64]
kmovd ebx, k0
popcnt ebx, ebx
add rbx, rax
vpcmpeqb k0, ymm3, ymmword ptr [rdx + r10 + 96]
kmovd eax, k0
popcnt eax, eax
add rax, rbx
sub r10, -128
add r9, -4
jne .LBB0_5
这是什么,展开了四次:
.LBB0_8: # =>This Inner Loop Header: Depth=1
vmovdqu ymm0, ymmword ptr [r10 + rbx]
vpcmpeqb k0, ymm0, ymmword ptr [r9 + rbx]
kmovd ecx, k0
popcnt ecx, ecx
add rax, rcx
add rbx, 32
cmp r8, rbx
jne .LBB0_8
尽管这个使用了AVX512VL指令,ICX还可以为AVX或AVX2做矢量化处理。
如果您想要对函数进行多线程处理和矢量化处理,在ICX/ICPX上添加"-fiopenmp",在Clang/GCC上添加"-fopenmp",并取消注释"#pragma omp"指令。不幸的是,这只接受一个严格的格式作为"for"语句,并需要在"for"周围嵌套一个"if"块(否则可以将其作为循环条件中的额外子句:"n > 31U && i < n - 31U")。
由于x96 CPU在数据对齐到16字节边界时更快地将数据加载到寄存器中,您还希望声明输入数组为"alignas(ALIGNMENT)"。
这是我能够做到的最快和最便携的版本。然而,你应该看看@harold提供的
这个非常相似问题的答案,它结合了一个4,096字节的外部循环和一个32字节的内部循环,然后还有一个执行垂直加法的第二个循环。内部循环也比原来短了一条指令。
-march=native
,这意味着他的CPU支持的任何-mfoo
标志。 - user784668