请考虑以下循环:
template <typename T>
void copytail(T* __restrict__ dest, const T* __restrict__ src, size_t count) {
constexpr size_t chunk_size = 4 * 32;
size_t byte_count = sizeof(T) * count;
size_t chunks = byte_count / chunk_size;
auto rest = byte_count - byte_count / chunk_size * chunk_size;
auto rest_vecs = (rest + 31) / 32;
__m256i* dest256 = (__m256i*)((char *)dest + byte_count - rest_vecs * 32);
__m256i* src256 = (__m256i*)((char *)src + byte_count - rest_vecs * 32);
for (size_t j = 0; j < rest_vecs; j++) {
_mm256_storeu_si256(dest256 + j, _mm256_loadu_si256(src256 + j));
}
}
void tail_copy(char* d, const char* s, size_t overshoot) {
copytail(d, s, overshoot);
}
不要太在意它的功能,因为它是基于更完整功能的简化测试用例-但基本上,它从src
到dest
最多复制4个AVX2向量,并与结束的区域。
由于任何原因1,-O3
处的gcc 8.1都会生成此奇数汇编:
tail_copy(char*, char const*, unsigned long):
mov rax, rdx
and eax, 127
add rax, 31
mov rcx, rax
and rcx, -32
sub rdx, rcx
shr rax, 5
je .L30
sal rax, 5
mov r8d, eax
add rdi, rdx
add rsi, rdx
test dil, 1
jne .L32
.L3:
test dil, 2
jne .L33
.L4:
test dil, 4
jne .L34
.L5:
mov ecx, r8d
shr ecx, 3
rep movsq # oh please no
xor eax, eax
test r8b, 4
jne .L35
test r8b, 2
jne .L36
# many more tail-handling cases follow
基本上是一次rep movsq
循环,一次复制8个字节,然后是一堆用于处理奇数字节的尾部处理代码(大多数未显示,可以在godbolt上看到完整的汇编)。
因此,我希望gcc或多或少以书面形式发出我的复制循环-至少32字节的AVX2加载和存储应出现在源代码中。重要的是,我希望它在此函数中是局部的:也就是说,不要更改编译器参数。
[1可能是memcpy
识别,然后是memcpy
内联。
您对memcpy
识别的假设似乎是正确的(__builtin_memcpy
首次出现在ldist
遍中,如在-fdump-tree-all
日志中可以看到,这阻碍了优化:
__attribute__ ((optimize ("no-tree-loop-distribute-patterns")))
void tail_copy(char* d, const char* s, size_t overshoot) {
copytail(d, s, overshoot);
}
将其应用于模板定义似乎也可行。
[如果CPU支持ERMS(就像大多数具有AVX2的Intel CPU一样),则尚不清楚这是否有所改进。