A função `tolower()` usando AVX-512
(dotat.at)-
Há alguns anos, escrevi sobre como acelerar
tolower()usando truques de SWAR. Alguns dias atrás, fiquei interessado em um texto de Olivier Giniaux sobre uma otimização para processar strings pequenas com instruções SIMD. Esse método é usado em uma função de hash rápida escrita em Rust. -
Instruções SIMD facilitam o processamento de strings curtas, mas sempre me incomodou a dificuldade de transferir dados entre a memória e os registradores vetoriais. O texto de Olivier apresentou uma maneira interessante de resolver esse problema.
Sinais de esperança
-
Alguns conjuntos de instruções SIMD oferecem funções úteis de carregamento e armazenamento com máscara para processamento de strings. Elas operam em nível de byte.
- ARM SVE: disponível em grandes núcleos ARM Neoverse recentes, como os do Amazon Graviton. Porém, não está disponível no Apple Silicon.
- AVX-512-BW: disponível em processadores AMD Zen recentes. AVX-512 é um conjunto de extensões complexo, e no caso da Intel o suporte é bastante aleatório.
-
Como eu tinha uma máquina com AMD Zen 4, decidi experimentar AVX-512-BW.
tolower64()
- Usei o guia de intrinsics da Intel para escrever uma função básica
tolower()capaz de processar 64 bytes de uma vez.- Pesquisei
mm512*epi8usando*como curinga para encontrar funções AVX-512 que operam em bytes. - Preenchi alguns registradores com 64 bytes úteis.
- Configurei os valores necessários para converter letras maiúsculas em minúsculas.
- Comparei os caracteres de entrada com A e Z para verificar se eram maiúsculos.
- Usei uma máscara para converter em minúsculas quando o caractere era maiúsculo.
- Pesquisei
Carregamento e armazenamento em massa
- Era preciso encapsular o kernel
tolower64()em uma função mais conveniente. Por exemplo, uma função que copia a string enquanto a converte para minúsculas.- Para strings longas, usei instruções de carregamento e armazenamento vetorial desalinhado.
Carregamento e armazenamento com máscara
- Para strings pequenas e para o final de strings longas, usei carregamento e armazenamento desalinhados com máscara.
- A máscara tem os primeiros
lenbits ativados. - O carregamento e o armazenamento são semelhantes às versões de largura total com uma máscara adicional.
- A máscara tem os primeiros
Benchmark
-
Foi feito benchmark do desempenho de várias funções semelhantes.
- Compilado com Clang 16 e executado em um AMD Ryzen 9 7950X.
- Cada função foi compilada separadamente para evitar interferência de inline e movimentação de código.
-
Resultados:
tolower64foi a função mais rápida entre todas as testadas.copybytes64usa AVX-512 de forma parecida comtolower64, mas não é muito mais rápida.copybytes1fazmemcpybyte a byte e mostra que a vetorização automática do Clang 11 é relativamente ruim.- A
tolower()padrão foi a mais lenta. tolower1é umatolower()byte a byte compilada com Clang 16; a vetorização automática melhorou, mas ainda é lenta.tolower8é atolower()com SWAR apresentada em um post anterior do blog; o Clang tenta vetorização automática, mas o resultado não é bom.memcpyé rápida no início, mas cai para metade da velocidade decopybytes64.
Conclusão
-
AVX-512-BW é muito útil, especialmente ao processar strings curtas.
-
No Zen 4, é muito rápido, e as funções intrínsecas são fáceis de usar.
-
O desempenho do AVX-512-BW é muito estável.
-
Não pude investigar em detalhe por não ter uma máquina com suporte a ARM SVE, mas fiquei curioso sobre o quão bem o SVE funciona com strings curtas.
-
Espero que essas extensões de conjunto de instruções sejam adotadas de forma mais ampla. Elas podem melhorar bastante o desempenho no processamento de strings.
-
O código deste post do blog pode ser visto no meu site.
Resumo do GN⁺
- Este texto explica como processar strings curtas com eficiência usando instruções SIMD.
- Mostra que os conjuntos de instruções AVX-512-BW e ARM SVE são úteis para processamento de strings.
- Nos benchmarks, o AVX-512-BW mostrou desempenho excelente, especialmente em strings curtas.
- O texto deve ser útil para desenvolvedores interessados em otimização de desempenho.
1 comentários
Comentários do Hacker News
No modelo de memória do Rust e do LLVM, o truque de "unsafe read beyond of death" é considerado comportamento indefinido
Surgiu curiosidade sobre a implementação de AVX512 da AMD e a disputa com o AVX10 da Intel
A otimização SWAR só é útil para strings alinhadas em endereços de 8 bytes
A adição de máscaras parece elegante
Usando Clang, é possível obter resultados melhores
O loop principal para strings curtas tem uma instrução a menos
Alguém escreveu em C# uma implementação semelhante para converter ASCII em maiúsculas/minúsculas em UTF-8
Há um exemplo de uso de SIMD com AVX512 para converter texto em uwu
Seria ainda mais impressionante se considerasse conversão de caracteres Unicode
No passado, houve a experiência de adicionar bordas pretas ao redor de imagens para evitar problemas de SIMD com buffers