libpopcnt.h
é uma biblioteca C/C++ somente de cabeçalho para contar o número de 1 bits (contagem de população de bits) em um array o mais rápido possível usando instruções especializadas de CPU, ou seja, POPCNT, AVX2, AVX512, NEON, SVE. libpopcnt.h
foi testado com sucesso usando os compiladores GCC, Clang e MSVC.
#include "libpopcnt.h"
/*
* Count the number of 1 bits in the data array
* @data: An array
* @size: Size of data in bytes
*/
uint64_t popcnt ( const void * data , uint64_t size );
libpopcnt.h
não requer nenhum sinalizador especial do compilador como -mavx2
! Para obter o melhor desempenho, recomendamos apenas compilar com otimizações habilitadas, por exemplo -O3
ou -O2
.
cc -O3 program.c
c++ -O3 program.cpp
libpopcnt.h
possui algoritmos popcount acelerados por hardware para as seguintes arquiteturas de CPU:
x86 | POPCNT , AVX2 , AVX512 |
x86-64 | POPCNT , AVX2 , AVX512 |
BRAÇO | NEON , SVE |
PPC64 | POPCNTD |
Para outras arquiteturas de CPU, um algoritmo popcount inteiro rápido é usado.
Em CPUs x86, libpopcnt.h
primeiro consulta os conjuntos de instruções suportados pela sua CPU usando a instrução CPUID
(isso é feito apenas uma vez). Então libpopcnt.h
escolhe o algoritmo de contagem de população de bits mais rápido suportado pela sua CPU:
AVX512
o algoritmo AVX512 VPOPCNT
será usado.AVX2
o algoritmo AVX2 Harley Seal
será usado.POPCNT
o algoritmo POPCNT
será usado.POPCNT
é usado um algoritmo inteiro portátil. Observe que libpopcnt.h
funciona em todas as CPUs (x86, ARM, PPC, WebAssembly, ...). É portátil por padrão e a aceleração de hardware só é habilitada se a CPU suportar. libpopcnt.h
também é seguro para threads.
Levamos o desempenho a sério, se você compilar usando, por exemplo, -march=native
em uma CPU x86 com suporte AVX512, todas as verificações CPUID
em tempo de execução serão removidas!
ARM SVE é um novo conjunto de instruções vetoriais para CPUs ARM que foi lançado pela primeira vez em 2020. ARM SVE suporta um comprimento de vetor variável de 128 a 2.048 bits. Conseqüentemente, os algoritmos ARM SVE podem ser muito mais rápidos do que os algoritmos ARM NEON, que são limitados a um comprimento de vetor de 128 bits.
O novo algoritmo ARM SVE popcount da libpopcnt é até 3x mais rápido que seu algoritmo ARM NEON popcount (em CPUs AWS Graviton3). Infelizmente, o despacho em tempo de execução para ARM SVE ainda não é bem suportado pelos compiladores GCC e Clang e libcs. Portanto, por padrão, apenas o algoritmo ARM NEON popcount (portátil) é habilitado ao usar libpopcnt em CPUs ARM.
Para habilitar o algoritmo ARM SVE popcount da libpopcnt você precisa compilar seu programa usando a opção ARM SVE do seu compilador, por exemplo:
gcc -O3 -march=armv8-a+sve program.c
g++ -O3 -march=armv8-a+sve program.cpp
cmake .
make -j
make test
Os comandos acima também constroem o programa benchmark
que é útil para benchmarking libpopcnt.h
. Abaixo está um exemplo de uso executado em uma CPU AMD EPYC 9R14 de 2023:
# Usage: ./benchmark [array bytes] [iters]
./benchmark
Iters: 10000000
Array size: 16.00 KB
Algorithm: AVX512
Status: 100%
Seconds: 1.23
133.5 GB/s
Alguns dos algoritmos usados em libpopcnt.h
são descritos no artigo Faster Population Counts using AVX2 Instructions de Daniel Lemire, Nathan Kurz e Wojciech Mula (23 de novembro de 2016). O algoritmo AVX2 Harley Seal popcount usado em libpopcnt.h
foi copiado do repositório GitHub sse-popcount de Wojciech Muła.