Qual é o algoritmo mais eficiente para obter o seguinte:
0010 0000 => 0000 0100
A conversão é de MSB-> LSB para LSB-> MSB. Todos os bits devem ser revertidos; isto é, não é troca de endianness.
c
algorithm
bit-manipulation
green_t
fonte
fonte
Respostas:
NOTA : Todos os algoritmos abaixo estão em C, mas devem ser portáteis para o idioma de sua escolha (apenas não olhe para mim quando não for tão rápido :)
Opções
Pouca memória (
int
máquina de 32 bits , 32 bits) ( daqui ):Na famosa página Bit Twiddling Hacks :
Mais rápido (tabela de pesquisa) :
Você pode estender essa idéia para
int
s de 64 bits ou trocar a memória por velocidade (supondo que o cache de dados L1 seja grande o suficiente) e reverter 16 bits por vez com uma tabela de pesquisa de entrada de 64K.Outras
Simples
Mais rápido (processador de 32 bits)
Mais rápido (processador de 64 bits)
Se você quiser fazer isso em 32 bits
int
, basta inverter os bits em cada byte e inverter a ordem dos bytes. Isso é:Resultados
Comparei as duas soluções mais promissoras, a tabela de pesquisa e AND bit a bit (a primeira). A máquina de teste é um laptop com 4 GB de DDR2-800 e um Core 2 Duo T7500 a 2,4 GHz, cache L2 de 4 MB; YMMV. Eu usei o gcc 4.3.2 no Linux de 64 bits. O OpenMP (e as ligações do GCC) foram usados para temporizadores de alta resolução.
reverse.c
reverse_lookup.c
Eu tentei ambas as abordagens em várias otimizações diferentes, executei três tentativas em cada nível e cada uma delas reverteu 100 milhões aleatoriamente
unsigned ints
. Para a opção de tabela de pesquisa, tentei os dois esquemas (opções 1 e 2) apresentados na página de hacks bit a bit. Os resultados são mostrados abaixo.AND bit a bit
Tabela de pesquisa (opção 1)
Tabela de pesquisa (opção 2)
Conclusão
Use a tabela de pesquisa, com a opção 1 (o endereçamento de bytes é surpreendentemente lento) se você estiver preocupado com o desempenho. Se você precisar extrair todo último byte de memória do seu sistema (e você pode, se se preocupa com o desempenho da inversão de bits), as versões otimizadas da abordagem AND bit a bit também não são muito ruins.
Embargo
Sim, eu sei que o código de referência é um hack completo. Sugestões sobre como melhorá-lo são mais que bem-vindas. Coisas que eu sei sobre:
ld
explodiu com algum erro de redefinição de símbolo maluco), então não acredito que o código gerado esteja ajustado para minha microarquitetura.32 bits
Edição: Eu também tentei usar
uint64_t
tipos na minha máquina para ver se houve algum aumento de desempenho. O desempenho foi cerca de 10% mais rápido que 32 bits e era quase idêntico se você estava apenas usando tipos de 64 bits para reverter bits em doisint
tipos de 32 bits por vez ou se você estava realmente invertendo os bits pela metade. valores de bits. O código de montagem é mostrado abaixo (no caso anterior, a reversão de bits para doisint
tipos de 32 bits por vez):fonte
Esse tópico chamou minha atenção, pois lida com um problema simples que requer muito trabalho (ciclos da CPU), mesmo para uma CPU moderna. E um dia eu também fiquei lá com o mesmo ¤ #% "#" problema. Eu tive que virar milhões de bytes. No entanto, eu sei que todos os meus sistemas de destino são modernos baseados em Intel, então vamos começar a otimizar ao extremo !!!
Então eu usei o código de pesquisa de Matt J como base. o sistema em que estou comparando é um i7 haswell 4700eq.
A pesquisa de Matt J transferiu bits 400 000 000 bytes: cerca de 0,272 segundos.
Fui em frente e tentei ver se o compilador ISPC da Intel poderia vetorizar a aritmética no sentido inverso.c.
Não vou aborrecê-lo com minhas descobertas aqui, já que tentei muito ajudar o compilador a encontrar coisas; de qualquer maneira, acabei com um desempenho de cerca de 0,15 segundos para alterar bit 400 000 000 bytes. É uma grande redução, mas para o meu aplicativo ainda é muito lento ..
Então, as pessoas me permitem apresentar o bitflipper baseado em Intel mais rápido do mundo. Cronometrado em:
Hora de converter bit 400000000 bytes: 0,050082 segundos !!!!!
Os printf são para depuração ..
Aqui está o cavalo de batalha:
O código ocupa 32 bytes e mascara os petiscos. A mordidela alta é deslocada para a direita em 4. Então eu uso vpshufb e ymm4 / ymm3 como tabelas de pesquisa. Eu poderia usar uma única tabela de pesquisa, mas teria que mudar para a esquerda antes de ORing os petiscos juntos novamente.
Existem maneiras ainda mais rápidas de virar os bits. Mas estou vinculado ao thread único e à CPU, então foi o mais rápido que consegui. Você pode fazer uma versão mais rápida?
Não faça comentários sobre o uso dos comandos equivalentes intrínsecos do compilador Intel C / C ++ ...
fonte
pshub
, porque, afinal, o melhor contador de histórias também é feito! Eu teria escrito aqui se não fosse por você. Parabéns.popcnt
,tzcnt
epext
tudo na porta 1. Então, todapext
outzcnt
custa umpopcnt
rendimento. Se seus dados estiverem quentes no cache L1D, a maneira mais rápida de contar uma matriz nas CPUs Intel é com o AVX2 pshufb. (A Ryzen tem umapopcnt
taxa de transferência de 4 por clock, então isso é provavelmente ideal, mas a família Bulldozer tem uma porpopcnt r64,r64
taxa de transferência de 4 relógios ... agner.org/optimize ).Essa é outra solução para quem gosta de recursão.
A ideia é simples. Divida a entrada pela metade e troque as duas metades, continue até atingir o bit único.
Aqui está uma função recursiva para resolvê-lo. (Observe que usei entradas não assinadas, para que funcione com entradas de tamanho até (int não assinado) * 8 bits.
Esta é a saída:
fonte
numBits
é int, quando você divide 3 por 2 para a função param, ela será arredondada para 1?Bem, isso certamente não será uma resposta como a de Matt J, mas espero que ainda seja útil.
Essa é exatamente a mesma idéia que o melhor algoritmo de Matt, exceto que existe esta pequena instrução chamada BSWAP que troca os bytes (não os bits) de um número de 64 bits. Então b7, b6, b5, b4, b3, b2, b1, b0 se tornam b0, b1, b2, b3, b4, b5, b6, b7. Como estamos trabalhando com um número de 32 bits, precisamos mudar nosso número de bytes trocados para 32 bits. Isso nos deixa com a tarefa de trocar os 8 bits de cada byte que está pronto e pronto! Foram realizadas.
Tempo: na minha máquina, o algoritmo de Matt foi executado em ~ 0,52 segundos por teste. A mina funcionou em cerca de 0,42 segundos por teste. 20% mais rápido não é ruim, eu acho.
Se você está preocupado com a disponibilidade da instrução BSWAP, a Wikipedia lista a instrução BSWAP como adicionada ao 80846, lançada em 1989. Deve-se notar que a Wikipedia também afirma que essa instrução só funciona em registros de 32 bits, o que claramente não é o Na minha máquina, ele funciona muito bem apenas em registros de 64 bits.
Este método funcionará igualmente bem para qualquer tipo de dados integral, para que o método possa ser generalizado trivialmente, passando o número de bytes desejados:
que pode ser chamado como:
O compilador deve ser capaz de otimizar o parâmetro extra (assumindo que o compilador alinha a função) e, para o
sizeof(size_t)
caso, o deslocamento à direita seria removido completamente. Observe que o GCC, pelo menos, não é capaz de remover o BSWAP e o deslocamento para a direita, se aprovadosizeof(char)
.fonte
unsigned long long int
que deve ser de pelo menos 64 bits, conforme aqui e aquiA resposta de Anders Cedronius fornece uma ótima solução para pessoas que têm uma CPU x86 com suporte para AVX2. Para plataformas x86 sem suporte para AVX ou plataformas não-x86, uma das seguintes implementações deve funcionar bem.
O primeiro código é uma variante do método clássico de particionamento binário, codificado para maximizar o uso do idioma shift-plus-logic útil em vários processadores ARM. Além disso, ele usa geração de máscara on-the-fly, o que pode ser benéfico para os processadores RISC que, caso contrário, exigem várias instruções para carregar cada valor de máscara de 32 bits. Compiladores para plataformas x86 devem usar propagação constante para calcular todas as máscaras no tempo de compilação, em vez do tempo de execução.
No volume 4A de "The Art of Computer Programming", D. Knuth mostra maneiras inteligentes de reverter bits que surpreendentemente requerem menos operações do que os algoritmos de particionamento binário clássicos. Um desses algoritmos para operandos de 32 bits, que não consigo encontrar no TAOCP, é mostrado neste documento no site do Hacker's Delight.
Usando o compilador Intel C / C ++ 13.1.3.198, as duas funções acima auto-vectorizam automaticamente os
XMM
registros de segmentação agradável . Eles também podem ser vetorizados manualmente sem muito esforço.No meu IvyBridge Xeon E3 1270v2, usando o código auto-vetorizado, 100 milhões de
uint32_t
palavras foram invertidas em 0,070 segundos usandobrev_classic()
e 0,068 segundos usandobrev_knuth()
. Tomei o cuidado de garantir que minha referência não fosse limitada pela largura de banda da memória do sistema.fonte
brev_knuth()
? A atribuição no PDF da Hacker's Delight parece indicar que esses números são diretamente do próprio Knuth. Não posso afirmar que compreendi a descrição de Knuth dos princípios de design subjacentes no TAOCP suficientemente para explicar como as constantes foram derivadas, ou como se poderia abordar as constantes derivadas e os fatores de deslocamento para tamanhos de palavras arbitrários.Supondo que você tenha uma matriz de bits, que tal: 1. A partir do MSB, insira os bits em uma pilha, um por um. 2. Coloque os bits desta pilha em outra matriz (ou a mesma matriz, se você quiser economizar espaço), colocando o primeiro bit exibido no MSB e passando para os bits menos significativos a partir daí.
fonte
A instrução nativa do ARM "rbit" pode fazer isso com 1 ciclo de CPU e 1 registro de CPU extra, impossível de bater.
fonte
Isso não é trabalho para um humano! ... mas perfeito para uma máquina
Estamos em 2015, 6 anos após a primeira pergunta. Os compiladores se tornaram nossos mestres e nosso trabalho como seres humanos é apenas ajudá-los. Então, qual é a melhor maneira de dar nossas intenções à máquina?
A reversão de bits é tão comum que você deve se perguntar por que o ISA cada vez maior do x86 não inclui uma instrução para fazê-lo de uma só vez.
O motivo: se você der sua verdadeira intenção concisa ao compilador, a reversão de bits deverá levar apenas ~ 20 ciclos de CPU . Deixe-me mostrar como criar reverse () e usá-lo:
A compilação deste programa de amostra com a versão Clang> = 3.6, -O3, -march = native (testada com Haswell) fornece código de qualidade de arte usando as novas instruções do AVX2, com um tempo de execução de 11 segundos processando ~ 1 bilhão de reversos () s. Isso é aproximadamente 10 ns por reverso (), com o ciclo de 0,5 ns da CPU assumindo que 2 GHz nos coloca nos 20 ciclos da CPU.
Advertência: esse código de amostra deve permanecer como uma referência decente por alguns anos, mas acabará por começar a mostrar sua idade, uma vez que os compiladores sejam inteligentes o suficiente para otimizar main () para imprimir apenas o resultado final, em vez de realmente calcular qualquer coisa. Mas, por enquanto, ele funciona em mostrar reverse ().
fonte
Bit-reversal is so common...
Eu não sei disso. Trabalho com código que lida com dados no nível de bits praticamente todos os dias e não me lembro de ter tido essa necessidade específica. Em quais cenários você precisa? - Não que não seja um problema interessante de resolver por si só.É claro que a fonte óbvia de hacks de manipulação de bits está aqui: http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious
fonte
Eu sei que não é C, mas asm:
Isso funciona com o bit de transporte, para que você também possa salvar sinalizadores
fonte
rcl
mudar CF paravar1
, em vez de apenas oshl
que não lê sinalizadores. (Ouadc dx,dx
) Mesmo com essa correção, isso é ridiculamente lento, usando asloop
instruções lentas e mantendo avar1
memória! Na verdade, acho que isso deveria estar produzindo a saída no AX, mas salva / restaura o valor antigo do AX por cima do resultado.Implementação com pouca memória e mais rápida.
fonte
Bem, isso é basicamente o mesmo que o primeiro "reverse ()", mas é de 64 bits e precisa apenas de uma máscara imediata para ser carregada do fluxo de instruções. O GCC cria código sem saltos, portanto isso deve ser bem rápido.
fonte
Fiquei curioso em saber quão rápido seria a rotação bruta óbvia. Na minha máquina (i7 @ 2600), a média para 1.500.150.000 iterações era
27.28 ns
(acima de um conjunto aleatório de 131.071 números inteiros de 64 bits).Vantagens: a quantidade de memória necessária é pequena e o código é simples. Eu diria que também não é tão grande assim. O tempo necessário é previsível e constante para qualquer entrada (128 operações SHIFT aritméticas + 64 operações AND lógicas + 64 operações OR lógicas).
Comparei com o melhor tempo obtido por @Matt J - que tem a resposta aceita. Se eu li sua resposta corretamente, o melhor que ele obteve foram
0.631739
segundos para as1,000,000
iterações, o que leva a uma média de631 ns
por rotação.O snippet de código que usei é este abaixo:
fonte
Você pode querer usar a biblioteca de modelos padrão. Pode ser mais lento que o código acima mencionado. No entanto, parece-me mais claro e fácil de entender.
fonte
Genérico
Código C. Usando dados de entrada de 1 byte num como exemplo.
fonte
Que tal o seguinte:
Pequeno e fácil (apenas 32 bits).
fonte
Eu pensei que esta é uma das maneiras mais simples de reverter o bit. informe-me se houver alguma falha nessa lógica. basicamente nessa lógica, verificamos o valor do bit na posição. defina o bit se o valor for 1 na posição invertida.
fonte
fonte
k
é sempre uma potência de 2, mas os compiladores provavelmente não provam isso e o transformam em bit-scan / shift.Eu acho que o método mais simples que conheço segue.
MSB
é entrada eLSB
saída 'invertida':fonte
fonte
Outra solução baseada em loop que sai rapidamente quando o número é baixo (em C ++ para vários tipos)
ou em C para um int não assinado
fonte
Parece que muitos outros posts estão preocupados com a velocidade (ou seja, melhor = mais rápido). E a simplicidade? Considerar:
e espero que o compilador inteligente otimize para você.
Se você deseja reverter uma lista mais longa de bits (contendo
sizeof(char) * n
bits), pode usar esta função para obter:Isso reverteria [10000000, 10101010] para [01010101, 00000001].
fonte
ith_bit = (c >> i) & 1
. Salve também um SUB deslocando emreversed_char
vez de mudar o bit, a menos que você espere que ele compile no x86 parasub something
/bts reg,reg
para definir o enésimo bit no registro de destino.Reversão de bits em pseudo código
origem -> byte a ser revertido b00101100 destino -> revertido, também precisa ser do tipo não assinado, para que o bit de sinal não seja propagado para baixo
copiar para temp, para que o original não seja afetado, também precisa ser do tipo não assinado, para que o bit de sinal não seja deslocado automaticamente
LOOP8: // faça este teste 8 vezes se a bytecopy for <0 (negativo)
fonte
Minha solução simples
fonte
i
? Além disso, o que é essa constante mágica* 4
? É issoCHAR_BIT / 2
?Isso é para 32 bits, precisamos alterar o tamanho se considerarmos 8 bits.
Lendo o número inteiro de entrada "num" na ordem LSB-> MSB e armazenando em num_reverse na ordem MSB-> LSB.
fonte
fonte