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 / 77489397
Accepted
Carl
Carl
Asked: 2023-11-16 00:26:35 +0800 CST2023-11-16 00:26:35 +0800 CST 2023-11-16 00:26:35 +0800 CST

Algoritmo SIMD para verificar se um bloco inteiro é "consecutivo".

  • 772

Como você verifica se um pedaço alinhado de 16 u32é consecutivo (e crescente)?

Por exemplo: [100, 101, 102, ..., 115]é. E [100, 99, 3 ...]não é.

Estou no AVX512f. Isto é o que tenho até agora:

Algo A:

* predefine DECREASE_U32, a u32x16 of [15,14,13,...0]
* let a = input + DECREASE_32 // wrapping is OK
* compare a to u32x16::splat(first_item(a))
* Return whether all true

Alterativa (Algo B)

* let b = copy of A
* permute the elements of b by one position
* let b = a-b
* Is b all 1's (except for 1st position)

Estou fazendo isso em Rust com a packed_simdcaixa, mas qualquer linguagem/pseudocódigo está bem. (Gostaria que houvesse uma operação SIMD para subtrair itens adjacentes.)

rust
  • 2 2 respostas
  • 54 Views

2 respostas

  • Voted
  1. Best Answer
    Peter Cordes
    2023-11-16T01:06:38+08:002023-11-16T01:06:38+08:00

    Acho que sua primeira ideia provavelmente será melhor se for feita dentro de um loop que possa amortizar o custo de carregamento de uma constante vetorial. AVX-512 pode fazer isso de forma eficiente.

    Ou com uma carga vetorial e depois transmitir separadamente o elemento baixo com vpbroadcastd, ou com uma carga vetorial e uma carga de transmissão. por exemplo vpaddd zmm16, zmm31, [rdi]{1to16}/ vpcmpeqd k1, zmm16, [rdi].

    Hmm, mas verificando se todos os elementos são verdadeiros, acho que talvez kaddwcom uma constante 1e verifique se os 16 bits inferiores são zero com kortest? Ou apenas kmovpara um registro inteiro para comparação, 0xffffcomo faríamos com SSE/AVX pmovmskb. Eu tentei isso e o clang teve uma ideia melhor: compare se não é igual e verifique se a máscara é zero. (ou seja, verifique se todos os elementos são iguais, verificando se eles não são diferentes.) Isso permite kortesta própria máscara. Apliquei a ideia do clang aos meus intrínsecos para que o GCC também pudesse fazer um conjunto melhor.

    Em C++:

    #include <immintrin.h>
    
    // compare for not-equal, checking the mask for 0
    bool check_contig(int *p)
    {
        __m512i bcast_first = _mm512_set1_epi32(*p);
        __m512i desired = _mm512_add_epi32(bcast_first, _mm512_setr_epi32(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0));
    
        __m512i v = _mm512_loadu_si512(p);
        __mmask16 cmp = _mm512_cmpneq_epi32_mask(desired, v);
        return cmp == 0;
    }
    

    Godbolt - asm do GCC e clang:

    # GCC
    check_contig(int*):
            vmovdqa32       zmm0, ZMMWORD PTR .LC0[rip]
            vpaddd  zmm0, zmm0, DWORD PTR [rdi]{1to16}
            vpcmpd  k0, zmm0, ZMMWORD PTR [rdi], 4
            kortestw        k0, k0
            sete    al
            vzeroupper
            ret
    
    # clang
    check_contig(int*):
            vpbroadcastd    zmm0, dword ptr [rdi]
            vpaddd  zmm0, zmm0, zmmword ptr [rip + .LCPI0_0]
            vpcmpneqd       k0, zmm0, zmmword ptr [rdi]
            kortestw        k0, k0
            sete    al
            vzeroupper
            ret
    

    Então, ambos optam por carregar duas vezes em vez de vpbroadcastd zmm1, xmm0, pelo menos quando não estão em um loop, então a constante do vetor também precisa ser carregada .rodata.

    Talvez se eu escrevesse de forma diferente, como _mm512_broadcastd_epi32( _mm512_castsi512_si128(v)), eles prefeririam uma carga, ao custo de um embaralhamento extra. (O que provavelmente é pior quando você tem uops de 512 bits em vôo, então as CPUs Intel desligam o vetor ALU na porta 1, deixando apenas as portas 0 e 5. https://agner.org/optimize/ e https://uops .info/ )


    Algo B - evitando uma constante vetorial não trivial

    Talvez sua segunda maneira também possa ser feita de forma eficiente para valigndgirar o vetor; a única constante vetorial necessária são todas aquelas que podem ser geradas de maneira um pouco mais barata ( vpternlogd) em vez de carregadas.

    Verificar a máscara de comparação provavelmente exigiria um kmovnúmero inteiro para um and+ cmpverificar todos os bits, exceto um, a menos que possamos usar o mesmo truque que o clang fez e organizar as coisas para que realmente queiramos que a máscara seja totalmente zero nos lugares que desejamos. Nesse caso, test eax, imm32podemos verificar os bits que queremos, ignorando os que não queremos.

    • 2
  2. Carl
    2023-11-16T03:25:11+08:002023-11-16T03:25:11+08:00

    O núcleo do meu código Rust atual agora é este código de macro:

        const LAST_INDEX: usize = <$simd>::lanes() - 1;
        let (expected, overflowed) = $chunk[0].overflowing_add(LAST_INDEX as $scalar);
        if overflowed || expected != $chunk[LAST_INDEX] {
            return false;
        }
    
    
        let a = unsafe { <$simd>::from_slice_aligned_unchecked($chunk) } + $decrease;
        let compare_mask = a.eq(<$simd>::splat(a.extract(0)));
        compare_mask.all()
    

    Onde $scalar é u32, $simd é u32x16e $decrease é o bloco [15, 14 ... 0]. A primeira parte do código verifica se o último elemento é 15 a mais que o primeiro (e cuida dos estouros).

    Pedi uma ferramenta inteligente para me ajudar a entender a montagem SIMD produzida. Diz:

    • vmovdqa64: Esta instrução move um vetor de dados de 512 bits para um registrador ZMM. É usado aqui duas vezes: vmovdqa64 zmm0,zmmword ptr [...]: Carrega um vetor de 512 bits da memória para zmm0. vmovdqa64 zmm0,zmmword ptr [...] (posteriormente no código): Carrega um vetor diferente de 512 bits em zmm0. vpaddd:

    • vpaddd zmm0,zmm0,zmmword ptr [rax+40h]: Executa adição de inteiros compactados de inteiros de 32 bits. Esta instrução adiciona o vetor de 512 bits em zmm0 a outro vetor de 512 bits (carregado do endereço de memória em rax + 40h) e armazena o resultado de volta em zmm0. vpbroadcastd:

    • vpbroadcastd zmm1,xmm0: transmite um número inteiro de 32 bits de xmm0 (128 bits inferiores de zmm0) em todas as pistas de zmm1. Isso cria um vetor de 512 bits em zmm1 onde todos os elementos são iguais e iguais ao valor em xmm0. vpcmpeqd:

    • vpcmpeqd k0,zmm0,zmm1: compara números inteiros de 32 bits em zmm0 e zmm1 quanto à igualdade. Os resultados são armazenados em um registrador máscara k0, onde cada bit representa o resultado da comparação para cada par de elementos. vpternlogd:

    • vpternlogd zmm1,zmm1,zmm1,0FFh: Executa uma operação lógica ternária bit a bit em cada bit dos operandos. A operação específica é determinada pelo valor imediato 0xFF, que neste caso corresponde a um OR bit a bit. vpmovm2d:

    • vpmovm2d zmm0,k0: Move a máscara de bits do registro de máscara k0 para um registro de uso geral zmm0. Cada bit de k0 se torna um elemento de 32 bits em zmm0. vpcmpd:

    • vpcmpd k0,zmm0,zmm1,4: Compara números inteiros de 32 bits em zmm0 e zmm1 de acordo com o predicado fornecido como o último operando (aqui 4, que normalmente representa "menor que"). O resultado é armazenado no registro de máscara k0. vmovdqu64:

    • vmovdqu64 zmmword ptr [rsp+50h],zmm0: Move o vetor de 512 bits em zmm0 para a memória no endereço rsp + 50h. curto:

    • kortestw k0,k0: testa o conteúdo do registro de máscara k0 e define o sinalizador zero com base no resultado. Isso é frequentemente usado para ramificações condicionais com base nos resultados de comparação do SIMD.

    • vzeroupper: Esta instrução é usada para limpar os 256 bits superiores de todos os registros YMM para evitar penalidades ao misturar AVX-512 e código SSE legado. É uma boa prática usar esta instrução antes de chamadas para funções que podem não estar cientes do AVX-512.

    • 1

relate perguntas

  • os braços de correspondência têm tipos incompatíveis esperados ao reutilizar a função dentro da correspondência

  • Conversão de tipo de ferrugem em uma instrução de correspondência

  • Como forçar o tipo de retorno de uma correspondência para ()?

  • enums de ferrugem em representações primitivas

  • Existe uma maneira de simplificar a correspondência diretamente para Ok("VAL") em Result<String, VarError>

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