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 / 77295776
Accepted
Some Name
Some Name
Asked: 2023-10-15 16:08:57 +0800 CST2023-10-15 16:08:57 +0800 CST 2023-10-15 16:08:57 +0800 CST

Como forçar o gcc a usar o avx2 para copiar uma estrutura de 32 bytes compartilhada entre threads?

  • 772

Considere o seguinte exemplo compilado com -O3 -march=native:

struct str{
    volatile uint64_t a1;
    volatile uint64_t a2;
    volatile uint64_t a3;
    volatile uint64_t a4;
};

int main(void){
    struct str str1;
    struct str str2;
    str1.a1 = str2.a2;
    str1.a2 = str2.a2;
    str1.a3 = str2.a3;
    str1.a4 = str2.a4;
}

Ele produz o seguinte código assembly:

main:
        push    rbp
        vpxor   xmm0, xmm0, xmm0
        vmovdqu8        YMMWORD PTR [rsp-32], ymm0
        mov     rbp, rsp
        mov     rax, QWORD PTR [rsp-24]
        mov     QWORD PTR [rsp-64], rax
        mov     rax, QWORD PTR [rsp-24]
        mov     QWORD PTR [rsp-56], rax
        mov     rax, QWORD PTR [rsp-16]
        mov     QWORD PTR [rsp-48], rax
        mov     rax, QWORD PTR [rsp-8]
        mov     QWORD PTR [rsp-40], rax
        xor     eax, eax
        vzeroupper
        pop     rbp
        ret

exemplo ao vivo de godbolt

Na minha máquina, KbL i7-8550Uele produz praticamente o mesmo código de máquina:

(gdb) disas main
Dump of assembler code for function main:
   pxor   xmm0,xmm0
   movaps XMMWORD PTR [rsp-0x28],xmm0
   mov    rax,QWORD PTR [rsp-0x20]
   movaps XMMWORD PTR [rsp-0x18],xmm0
   mov    QWORD PTR [rsp-0x48],rax
   mov    rax,QWORD PTR [rsp-0x20]
   mov    QWORD PTR [rsp-0x40],rax
   mov    rax,QWORD PTR [rsp-0x18]
   mov    QWORD PTR [rsp-0x38],rax
   mov    rax,QWORD PTR [rsp-0x10]
   mov    QWORD PTR [rsp-0x30],rax
   xor    eax,eax
   ret    

Na minha máquina há suporte para avx2, mas não SIMDé usado para copiar.

Como sugerir gcco uso 256 bitbaseado SIMD(já que a estrutura tem 256 bytes de tamanho)?

c
  • 1 1 respostas
  • 58 Views

1 respostas

  • Voted
  1. Best Answer
    Peter Cordes
    2023-10-15T17:58:10+08:002023-10-15T17:58:10+08:00

    O GCC fará cada volatileacesso com um acesso separado em ASM, não como um elemento de um vetor SIMD. Se esses fossem endereços MMIO, isso seria necessário para a correção. Ao usar volatile, você proíbe o GCC de fazer a otimização desejada!

    Para testar como algo é compilado, escreva uma função que receba ponteiros ou atue em globais, conforme discutido em Como remover "ruído" da saída do assembly GCC/clang? . Consulte https://godbolt.org/z/5h9Gc9o9c : sem volatile, GCC e clang -march=skylakeusam AVX2 para seu embaralhamento conforme escrito ou para cópia direta com carregamento/armazenamento de 256 bits se você não duplicar a2.


    Usei volátil para evitar que todo o armazenamento/carga fosse otimizado, já que a estrutura deve ser compartilhada entre diferentes threads.

    Se você for usar volatilecada um uint64_tseparadamente, você poderia ter usado apenas _Atomic(com memory_order_relaxedou release.)

    As regras de ordenação de memória x86, por sua vez, garantem a ordenação correta da memória.

    Você não pode contar com a ordem disso porque volatilenão faz nada para impedir a reordenação em tempo de compilação , exceto acessos voláteis em relação a outros volatiles, portanto você não terá sincronização garantida de aquisição/liberação. É muito parecido memory_order_relaxed.

    _Atomic uint64_tcom memory_order_acquire/ releaselhe daria a mesma geração de código, volatilemas com comportamento garantido. Quando usar volátil com multithreading? - praticamente nunca, a menos que você não consiga _Atomicfazer std::atomic<>um bom trabalho, como neste caso em que nem ele nem volatile uint64_tfará exatamente o que você deseja.

    struct str{
    // _Alignas(32)    // probably a good idea
      _Atomic uint64_t a1;  // or atomic_uint_least64_t, or volatile
      _Atomic uint64_t a2;
      _Atomic uint64_t a3;
      _Atomic uint64_t a4;
    };
    
    // I don't really recommend this; it ignores the _Atomic qualifier on members
    void copy2(volatile struct str *str1, volatile const struct str *str2) {
        *str1 = *str2;
    }
    

    Raio Deus

    # GCC13 and Clang 17 both make the same asm  -O3 -march=skylake
    copy2:
            vmovdqu ymm0, YMMWORD PTR [rsi]
            vmovdqu YMMWORD PTR [rdi], ymm0
            vzeroupper
            ret
    

    Mas cuidado, isso não funcionará em C++; A atribuição de struct se transforma em uma cópia por elemento e std::atomic<>o construtor de cópia de é excluído. Ou com volatile, o construtor de cópia implícito não está marcado volatile, portanto não copiará uma estrutura com volatilemembros.

    Infelizmente, GCC e Clang não fazem nenhuma suposição sobre a atomicidade por elemento de carga/armazenamento vetorial e coleta/dispersão? então _Atomic uint64_tos membros resultam na cópia por elemento, e não conheço uma ótima maneira de contornar isso sem algum código hackeado. https://godbolt.org/z/8zGE4soMe . (E eles não otimizam os atômicos de qualquer maneira; os componentes internos do compilador provavelmente tratam os atômicos de maneira muito semelhante volatile, já que essa é uma maneira de garantir que eles não sejam otimizados.)

    Se a estrutura estivesse alinhada por 16, seria 100% seguro e garantido no papel copiá-la como duas metades de 128 bits com movapsou vmovdqa, em CPUs com AVX (pelo menos Intel), já que a Intel finalmente conseguiu documentar que AVX implica Atomicidade de carga/armazenamento de 128 bits para acessos alinhados . Instruções SSE: quais CPUs podem realizar operações atômicas de memória de 16B?


    Um hack do mundo real para obter o que você deseja, bastante seguro/à prova de futuro

    Considere alinhar sua estrutura em 32 bytes ( _Alignas(32)no primeiro membro) e usá-la volatile __m256i*para copiá-la. (Desreferenciar o ponteiro diretamente, não use _mm256_load_si256.) É exatamente como usar, volatile uint64_t*mas você está forçando o compilador a fazer um acesso de 32 bytes em vez de quatro acessos de 8 bytes.

    Toda a cópia da estrutura será atômica na prática em CPUs modernas, exceto para Alder Lake E-cores, onde cada metade de 128 bits será atômica. https://rigtorp.se/isatomic/.

    Não conheço nenhum que rasgue em pedaços de 8 bytes, que é tudo o que você obtém da interpretação do GCC de volatilefornecer acessos livres de rasgos até a largura do registro inteiro, adequado para como o kernel do Linux o usa para atômicos. (Veja os comentários sobre esta resposta para o GCC evitando um armazenamento não atômico para voláteis no AArch64.)

    Mais importante ainda, não existe um mecanismo plausível para uma CPU rasgar os elementos de um amplo armazenamento SIMD, mesmo que ela seja dividida em pedaços de 8 ou 16 bytes para armazenar separadamente. Como está alinhado, os pedaços parciais de 8 bytes também estão alinhados, especialmente quando todo o vetor está alinhado naturalmente. No papel, acessos maiores que 16 bytes (ou maiores que 8 sem AVX) não têm garantias, portanto um Deathstation 9000 x86 poderia quebrar esse código, mas acessar o cache mais de uma vez para o mesmo pedaço de 8 bytes não faz sentido prático.

    Usar _mm256_loadu_si256sem alinhamento não funcionaria porque não é volatile. (No GNU C, volatileé mais ou menos bem definido e suportado para rolar seus próprios relaxedatômicos. E não fique tentado a usar asm("" ::: "memory")para forçar uma carga ou armazenamento em torno de acessos não voláteis: veja Quem tem medo de uma grande otimização ruim compilador? para algumas das otimizações mais obscuras que podem incomodar você, como inventar cargas extras para algo não volátil que a fonte lê uma vez.)

    Usar seu próprio __attribute__((aligned(1),vector_size(32),may_alias))vetor não alinhado para permitir volatile*seria uma cópia de 32 bytes com carregamento/armazenamento de instrução única ( ou metades de 16 bytes, dependendo das configurações de ajuste ). Mas se foi alinhado apenas por 8, no papel você não tem garantias sobre a atomicidade, mesmo dentro dos elementos de 8 bytes. E na prática você pode romper entre os elementos. Provavelmente é melhor alinhá-lo naturalmente com ele, definitivamente dentro de uma linha de cache, e não dividido entre bancos de cache ou qualquer outra coisa em algumas CPUs AMD.

    • 4

relate perguntas

  • Multiplicação mais rápida que *

  • Usando uma macro para comprimento de string no especificador de formato scanf () em C

  • Como você pode definir o tipo de dados de #define para long double?

  • Ponteiros const incompatíveis

  • Mudança de cor não gradual no OpenGL

Sidebar

Stats

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

    destaque o código em HTML usando <font color="#xxx">

    • 2 respostas
  • Marko Smith

    Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}?

    • 1 respostas
  • Marko Smith

    Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)?

    • 2 respostas
  • Marko Smith

    Por que as compreensões de lista criam uma função internamente?

    • 1 respostas
  • Marko Smith

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

    • 1 respostas
  • Marko Smith

    java.lang.NoSuchMethodError: 'void org.openqa.selenium.remote.http.ClientConfig.<init>(java.net.URI, java.time.Duration, java.time.Duratio

    • 3 respostas
  • Marko Smith

    Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)?

    • 4 respostas
  • Marko Smith

    Por que o construtor de uma variável global não é chamado em uma biblioteca?

    • 1 respostas
  • Marko Smith

    Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto?

    • 1 respostas
  • Marko Smith

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

    • 1 respostas
  • Martin Hope
    fbrereto Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}? 2023-12-21 00:31:04 +0800 CST
  • Martin Hope
    比尔盖子 Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)? 2023-12-17 10:02:06 +0800 CST
  • Martin Hope
    Amir reza Riahi Por que as compreensões de lista criam uma função internamente? 2023-11-16 20:53:19 +0800 CST
  • Martin Hope
    Michael A formato fmt %H:%M:%S sem decimais 2023-11-11 01:13:05 +0800 CST
  • Martin Hope
    God I Hate Python std::views::filter do C++20 não filtrando a visualização corretamente 2023-08-27 18:40:35 +0800 CST
  • Martin Hope
    LiDa Cute Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)? 2023-08-24 20:46:59 +0800 CST
  • Martin Hope
    jabaa Por que o construtor de uma variável global não é chamado em uma biblioteca? 2023-08-18 07:15:20 +0800 CST
  • Martin Hope
    Panagiotis Syskakis Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto? 2023-08-17 21:24:06 +0800 CST
  • Martin Hope
    Alex Guteniev Por que os compiladores perdem a vetorização aqui? 2023-08-17 18:58:07 +0800 CST
  • Martin Hope
    wimalopaan Somente operações bit a bit para std::byte em C++ 17? 2023-08-17 17:13:58 +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