g++
генерирует дополнительные инструкции по перемещению, и я не уверен почему. Годболт ссылка
Это происходит вокруг _mm512_dpbusds_epi32 intrinsi c. Инструкция вычисляет 8-разрядные точечные произведения, а затем добавляет их в упакованный 32-разрядный аккумулятор (в данном случае с добавлением насыщения). Инструкция немного необычна тем, что она читает и записывает в аккумулятор.
При компиляции с g cc компилятор выдает дополнительные инструкции перемещения (vmovdqa64
) на аккумулятор.
Вот тестовая программа, которая накапливает некоторые точечные продукты:
#include <immintrin.h>
#include <cstddef>
__m512i Slow(const __m512i *a, const __m512i b0, const __m512i b1, std::size_t count) {
__m512i c0 = _mm512_setzero_epi32();
__m512i c1 = _mm512_setzero_epi32();
for (std::size_t i = 0; i < count; ++i) {
c0 = _mm512_dpbusds_epi32(c0, a[i], b0);
c1 = _mm512_dpbusds_epi32(c1, a[i], b1);
}
// Do not optimize away
return _mm512_sub_epi32(c0, c1);
}
При компиляции с g++ -O3 -mavx512vnni example.cc -S
это основная l oop:
.L3:
vmovdqa64 (%rdi), %zmm6
vmovdqa64 %zmm3, %zmm0
vmovdqa64 %zmm4, %zmm2
addq $64, %rdi
vpdpbusds %zmm5, %zmm6, %zmm0
vpdpbusds %zmm1, %zmm6, %zmm2
vmovdqa64 %zmm0, %zmm3
vmovdqa64 %zmm2, %zmm4
cmpq %rdi, %rax
jne .L3
Приведенная выше сборка копирует аккумулятор из zmm3
в zmm0
, обновляет zmm0
и копирует его обратно в zmm3
. Это не нужно; он должен просто использовать один из zmm0
или zmm3
в качестве аккумулятора.
Проблема одинакова на g++ (Gentoo 9.2.0-r2 p3) 9.2.0
и g++ (Ubuntu 8.4.0-1ubuntu1~18.04) 8.4.0
.
clang++
9.0.1 позволяет избежать ненужного копирования (также развернуло l oop, но вот самая крутая версия.)
.LBB0_6: # =>This Inner Loop Header: Depth=1
vmovaps (%rdi), %zmm4
vpdpbusds %zmm0, %zmm4, %zmm3
vpdpbusds %zmm1, %zmm4, %zmm2
addq $64, %rdi
addq $-1, %rax
jne .LBB0_6
Мне удалось обойти проблема в g++
с помощью встроенного ассм.
#include <immintrin.h>
#include <cstddef>
__m512i Fast(const __m512i *a, const __m512i b0, const __m512i b1, std::size_t count) {
__m512i c0 = _mm512_setzero_epi32();
__m512i c1 = _mm512_setzero_epi32();
for (std::size_t i = 0; i < count; ++i) {
asm ("vpdpbusds %2, %1, %0" : "+x"(c0) : "x"(a[i]), "mx"(b0));
asm ("vpdpbusds %2, %1, %0" : "+x"(c1) : "x"(a[i]), "mx"(b1));
}
// Do not optimize away
return _mm512_sub_epi32(c0, c1);
}
l oop g++
генерирует для Fast
намного лучше:
.L3:
#APP
# 7 "asm.cc" 1
vpdpbusds (%rdi), %zmm3, %zmm0
# 0 "" 2
# 8 "asm.cc" 1
vpdpbusds (%rdi), %zmm1, %zmm2
# 0 "" 2
#NO_APP
addq $64, %rdi
cmpq %rax, %rdi
jne .L3