Рассмотрим следующий цикл:
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);
}
Не слишком задумывайтесь о том, что он делает, так как это сокращенный контрольный пример, основанный на более полной функции - но в основном он копирует до 4 AVX2векторы от src
до dest
, выровненные по концу областей.
По какой-либо причине 1 , gcc 8.1 при -O3
создает это странноесборка:
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
для вызова микрокода для основной копии, а затем набор кода обработки хвоста для обработки нечетных байтов (большинство не показано, полную сборку можно увидеть на godbolt ).
Это на порядок медленнее, чем vmovdqu
загрузка / хранение в моем случае.
И даже если он собирался использовать rep movs
ЦП имеет ERMSB, поэтому rep movsb
может, вероятно, выполнять точное количество байтов без дополнительной очистки, необходимой примерно так же эффективно, как rep movsq
. Но у процессора нет есть функция "fast short rep" (Ice Lake), поэтому у нас rep movs
накладные расходы при запуске - большая проблема.
Я бы хотел, чтобы gcc выдал свою копиюцикл более или менее, как написано - по крайней мере 32-байтовые загрузки AVX2 и хранилище должны отображаться как в исходном коде. Важно отметить, что я хочу, чтобы это было локально для этой функции: то есть не изменять аргументы компилятора.
1 Возможно, это memcpy
распознавание, за которым следует memcpy
вставка.