O código a seguir produz um assembly que executa condicionalmente o SIMD no GCC 12.3 quando compilado com -O3
. Para completar, o código sempre executa SIMD no GCC 13.2 e nunca executa SIMD no clang 17.0.1.
#include <array>
__attribute__((noinline)) void fn(std::array<int, 4>& lhs, const std::array<int, 4>& rhs)
{
for (std::size_t idx = 0; idx != 4; ++idx) {
lhs[idx] = lhs[idx] + rhs[idx];
}
}
Aqui está o link em godbolt.
Aqui está a montagem real do GCC 12.3 (com -O3):
fn(std::array<int, 4ul>&, std::array<int, 4ul> const&):
lea rdx, [rsi+4]
mov rax, rdi
sub rax, rdx
cmp rax, 8
jbe .L2
movdqu xmm0, XMMWORD PTR [rsi]
movdqu xmm1, XMMWORD PTR [rdi]
paddd xmm0, xmm1
movups XMMWORD PTR [rdi], xmm0
ret
.L2:
mov eax, DWORD PTR [rsi]
add DWORD PTR [rdi], eax
mov eax, DWORD PTR [rsi+4]
add DWORD PTR [rdi+4], eax
mov eax, DWORD PTR [rsi+8]
add DWORD PTR [rdi+8], eax
mov eax, DWORD PTR [rsi+12]
add DWORD PTR [rdi+12], eax
ret
Estou muito interessado em saber a) o propósito das primeiras 5 instruções de montagem eb) se há algo que possa ser feito para fazer com que o GCC 12.3 emita o código do GCC 13.2 (idealmente, sem escrever manualmente o SSE).
Parece que o GCC12 está tratando a
class
referência como se fosse um simplesint *
, em termos de selhs
erhs
poderia se sobrepor parcialmente .A sobreposição exata seria adequada, se
lhs[idx]
for o mesmo int querhs[idx]
, lemos duas vezes antes de escrevê-lo. Mas com sobreposição parcial,rhs[3]
por exemplo poderia ter sido atualizado por um doslhs[0..2]
acréscimos, o que não aconteceria com o SIMD se fizéssemos todas as cargas antes de qualquer uma das lojas.O GCC13 sabe que os objetos de classe não podem se sobrepor parcialmente (exceto para coisas comuns de sequência inicial para diferentes tipos de estrutura/classe, o que acho que não se aplica aqui). Isso seria UB, então podemos assumir que isso não acontece. A geração de código do GCC12 é uma otimização perdida.
Então, como podemos ajudar o GCC12? A opção usual é
__restrict
remover verificações de sobreposição ou ativar a vetorização automática quando o compilador não deseja inventar verificações + um substituto. Em C,restrict
faz parte da linguagem, mas em C++ é apenas uma extensão. (Suportado pelos principais compiladores convencionais, e você pode usar o pré-processador#define
para a string vazia em outros.) Você pode usar__restrict
referências e também ponteiros. (Pelo menos o GCC e o Clang aceitam sem avisos-Wall
; não verifiquei os documentos para ter certeza de que isso é padrão.)Ou leia tudo manualmente
lhs
antes de escrever qualquer coisaComo o seu
array
é pequeno o suficiente para caber em um registro SIMD, não há ineficiência na cópia. Isso seria ruimarray<int, 1000>
ou algo assim!Ambos são compilados no mesmo conjunto autovetorizado do GCC13, sem instruções desperdiçadas ( Godbolt )
O alinhamento promissor (como
alignas(16)
um dos tipos?) Poderia permitir o uso depaddd xmm1, [rdi]
, um operando de origem de memória, sem AVX.