AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79156564
Accepted
eyelash
eyelash
Asked: 2024-11-05 02:19:15 +0800 CST2024-11-05 02:19:15 +0800 CST 2024-11-05 02:19:15 +0800 CST

Compreendendo a otimização SIMD do Clang para multiplicar um float por um contador de loop int

  • 772

Dada a seguinte função

void foo(float* result, int size, float y, float delta) {
    for (int t = 0; t < size; ++t) {
        result[t] = y + delta * t;
    }
}

O Clang -O2 gera o seguinte assembly x86-64 :

.LCPI0_0:
        .long   0
        .long   1
        .long   2
        .long   3
.LCPI0_1:
        .long   4
        .long   4
        .long   4
        .long   4
.LCPI0_2:
        .long   65535
        .long   65535
        .long   65535
        .long   65535
.LCPI0_3:
        .long   1258291200
        .long   1258291200
        .long   1258291200
        .long   1258291200
.LCPI0_4:
        .long   1392508928
        .long   1392508928
        .long   1392508928
        .long   1392508928
.LCPI0_5:
        .long   0x53000080
        .long   0x53000080
        .long   0x53000080
        .long   0x53000080
.LCPI0_6:
        .long   8
        .long   8
        .long   8
        .long   8
foo(float*, int, float, float):
        test    esi, esi
        jle     .LBB0_7
        mov     eax, esi
        cmp     esi, 7
        ja      .LBB0_3
        xor     ecx, ecx
        jmp     .LBB0_6
.LBB0_3:
        mov     ecx, eax
        and     ecx, 2147483640
        movaps  xmm2, xmm1
        shufps  xmm2, xmm1, 0
        movaps  xmm3, xmm0
        shufps  xmm3, xmm0, 0
        mov     edx, eax
        shr     edx, 3
        and     edx, 268435455
        shl     rdx, 5
        movdqa  xmm4, xmmword ptr [rip + .LCPI0_0]
        xor     esi, esi
        movdqa  xmm5, xmmword ptr [rip + .LCPI0_1]
        movdqa  xmm6, xmmword ptr [rip + .LCPI0_2]
        movdqa  xmm7, xmmword ptr [rip + .LCPI0_3]
        movdqa  xmm8, xmmword ptr [rip + .LCPI0_4]
        movaps  xmm9, xmmword ptr [rip + .LCPI0_5]
        movdqa  xmm10, xmmword ptr [rip + .LCPI0_6]
.LBB0_4:
        movdqa  xmm11, xmm4
        paddd   xmm11, xmm5
        movdqa  xmm12, xmm4
        pand    xmm12, xmm6
        por     xmm12, xmm7
        movdqa  xmm13, xmm4
        psrld   xmm13, 16
        por     xmm13, xmm8
        subps   xmm13, xmm9
        addps   xmm13, xmm12
        movdqa  xmm12, xmm11
        pand    xmm12, xmm6
        por     xmm12, xmm7
        psrld   xmm11, 16
        por     xmm11, xmm8
        subps   xmm11, xmm9
        addps   xmm11, xmm12
        mulps   xmm13, xmm2
        addps   xmm13, xmm3
        mulps   xmm11, xmm2
        addps   xmm11, xmm3
        movups  xmmword ptr [rdi + rsi], xmm13
        movups  xmmword ptr [rdi + rsi + 16], xmm11
        paddd   xmm4, xmm10
        add     rsi, 32
        cmp     rdx, rsi
        jne     .LBB0_4
        cmp     ecx, eax
        je      .LBB0_7
.LBB0_6:
        xorps   xmm2, xmm2
        cvtsi2ss        xmm2, ecx
        mulss   xmm2, xmm1
        addss   xmm2, xmm0
        movss   dword ptr [rdi + 4*rcx], xmm2
        inc     rcx
        cmp     rax, rcx
        jne     .LBB0_6
.LBB0_7:
        ret

Estou tentando entender o que está acontecendo aqui. Parece que .LBB0_4é um loop que cobre 8 iterações do loop original para cada iteração (há 2 mulpsinstruções e cada instrução cobre 4 floats e rsié incrementada em 32). O código no final provavelmente está lá para cobrir o caso em que sizenão é divisível por 8. O que estou tendo problemas é o resto do código. O que todas essas outras instruções dentro do .LBB0_4loop e as constantes no início estão fazendo? Existe uma ferramenta ou um argumento do compilador que pode me ajudar a entender o resultado da vetorização SIMD? Talvez algo que transforme isso de volta em C++ com intrínsecos SIMD?

Também se eu mudar o código para isso

void foo(float* result, int size, float y, float delta) {
    for (int t = 0; t < size; ++t) {
        result[t] = y;
        y += delta;
    }
}

O Clang gera uma montagem muito menor e faz um loop em 16 valores de uma só vez .

Edição: Acabei de perceber que esta versão não é vetorizada e, portanto, é menor e provavelmente mais lenta.

Qual é a maneira mais rápida de escrever este código?

c++
  • 1 1 respostas
  • 126 Views

1 respostas

  • Voted
  1. Best Answer
    Peter Cordes
    2024-11-05T03:11:02+08:002024-11-05T03:11:02+08:00

    Como @user555045 apontou, esta é uma regressão no Clang 19. O Clang 18 e versões anteriores autovetorizam da maneira óbvia, com o loop principal usando cvtdq2pspara converter 4 int32_tem 4 float.

    Se observarmos a saída do Clang 19 para outros ISAs SIMD, como AArch64 e AVX-512 ( Godbolt ), em ambos os casos ele é usado unsignedpara floatconversões, como AArch64 ucvtfou AVX-512 vcvtudq2ps.

    O lance do bithack é como o clang vetoriza o u32 para float para ISAs como SSE2 e AVX2, que fornecem apenas int assinados de/para float.

    Então, ele deu um tiro no próprio pé (para AVX2 e versões anteriores) ao provar que ele int tsempre foi não negativo e substituí-lo por um temporário não assinado, esquecendo que ele também pode funcionar como assinado.

    Este é um bug que você deve relatar em https://github.com/llvm/llvm-project/issues/. Sinta-se à vontade para citar e/ou linkar para esta resposta. Sua fonte C++ é um bom caso de teste MCVE para um relatório de bug. Não vejo uma duplicata existente pesquisando vectorization unsigned floate coisas semelhantes.


    O loop executaria zero iterações se sizefosse assinado negativo, e é uma <comparação, então ele sempre sai do loop sem encontrar o estouro de sinal UB. Que é UB de qualquer forma, então o compilador pode simplesmente assumir que isso não acontece mesmo com uma t <= sizecondição ou algo assim. É isso que permite promover intcontadores de loop para largura de ponteiro para que eles possam ser usados ​​como índices de array sem refazer a extensão de sinal toda vez.


    Qual é a maneira mais rápida de escrever este código?

    Veja Por que esse código é executado mais lentamente após multiplicações de redução de força para adições transportadas por loop? - evitar a conversão e multiplicação (ou FMA) a cada iteração pode ser uma vitória, mas somente se você desenrolar o suficiente para esconder a latência de FP com múltiplos acumuladores de vetor. Como float y[UNROLL] = ...; y[0] += UNROLL * delta; y[1] += UNROLL * delta;... etc.

    Com desenrolamento suficiente para que o compilador use 6, 8 ou 10 vetores para elementos de y[], isso é apenas o suficiente para o produto latência x throughput de vaddpsno x86 moderno, tipicamente latência de 3 a 4 ciclos com throughput de 2/clock, então 6 a 8 em voo ao mesmo tempo. Alguma folga permite agendamento imperfeito.

    No Ice Lake ou Intel posterior, e talvez Zen 3 ou AMD posterior, isso poderia atingir 2x 256 bits vaddpse 2x 256 bits de armazenamento vetorial por clock. (Skylake e versões anteriores só podem fazer um armazenamento por clock. https://uops.info/ . O Q&A vinculado era sobre um polinômio de segunda ordem, então levou 2 adições por armazenamento com o método das diferenças. Então meu Skylake foi capaz de fazer 2 adições e 1 armazenamento por clock com meu código. O Ice Lake teria largura de banda de armazenamento de sobra com ele. Claro, assumindo que o destino estava ativo no cache L1d, caso contrário, você provavelmente só tem um gargalo de largura de banda de memória/cache, mesmo para L2.)

    Em núcleos Intel E (SIMD de 128 bits de largura), a taxa de transferência de adição para armazenamento também é de 1:1, então, novamente, você vai querer a versão não processada que apenas faz adições de SIMD FP.

    Adicionar, converter e FMA inteiros são apenas 3 uops ALU de vetor SIMD por clock, então poderia realmente rodar em 1 resultado de vetor por clock, o que é suficiente para acompanhar o rendimento do armazenamento no Skylake sem redução de força para apenas adicionar. (Se você compilar com, -march=x86-64-v3é claro, para permitir FMA). Mas então o front-end é um gargalo, já que tem apenas 4 uops de largura no SKL e anteriores. Então, a sobrecarga do loop (pelo menos um add ou sub/jcc com fusão de macro, provavelmente também um incremento de ponteiro) consome o rendimento e não deixa o exec fora de ordem avançar.

    • 6

relate perguntas

  • Por que os compiladores perdem a vetorização aqui?

  • Erro de compilação usando CMake com biblioteca [fechada]

  • Erro lançado toda vez que tento executar o premake

  • Como criar um tipo de octeto semelhante a std::byte em C++?

  • Somente operações bit a bit para std::byte em C++ 17?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve