Почему g cc добавляет дополнительные инструкции vmovdqa64 с _mm512_dpbusds_epi32? - PullRequest
4 голосов
/ 19 апреля 2020

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

1 Ответ

2 голосов
/ 21 апреля 2020

Отвечая на мой собственный вопрос. Это ошибка https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94663, вероятно, во встроенном распределителе регистров.

...