Quel est l'algorithme le plus efficace pour atteindre les objectifs suivants:
0010 0000 => 0000 0100
La conversion est de MSB-> LSB en LSB-> MSB. Tous les bits doivent être inversés; c'est-à-dire qu'il ne s'agit pas d' échanger l'endianité.
c
algorithm
bit-manipulation
green_t
la source
la source
Réponses:
REMARQUE : Tous les algorithmes ci-dessous sont en C, mais devraient être portables dans la langue de votre choix (ne me regardez pas quand ils ne sont pas aussi rapides :)
Les options
Mémoire faible (
int
machine 32 bits , 32 bits) (d' ici ):De la célèbre page Bit Twiddling Hacks :
Le plus rapide (table de recherche) :
Vous pouvez étendre cette idée à 64 bits
int
, ou échanger la mémoire pour la vitesse (en supposant que votre cache de données L1 est suffisamment grand), et inverser 16 bits à la fois avec une table de recherche à 64 Ko.Autres
Facile
Plus rapide (processeur 32 bits)
Plus rapide (processeur 64 bits)
Si vous souhaitez le faire sur un 32 bits
int
, inversez simplement les bits de chaque octet et inversez l'ordre des octets. C'est:Résultats
J'ai comparé les deux solutions les plus prometteuses, la table de recherche et Bitwise-AND (la première). La machine de test est un ordinateur portable avec 4 Go de DDR2-800 et un Core 2 Duo T7500 @ 2,4 GHz, 4 Mo de cache L2; YMMV. J'ai utilisé gcc 4.3.2 sur Linux 64 bits. OpenMP (et les liaisons GCC) ont été utilisés pour les temporisateurs haute résolution.
reverse.c
reverse_lookup.c
J'ai essayé les deux approches avec plusieurs optimisations différentes, j'ai effectué 3 essais à chaque niveau et chaque essai a inversé 100 millions au hasard
unsigned ints
. Pour l'option de table de recherche, j'ai essayé les deux schémas (options 1 et 2) donnés sur la page de piratage au niveau du bit. Les résultats sont présentés ci-dessous.ET au niveau du bit
Table de recherche (option 1)
Table de recherche (option 2)
Conclusion
Utilisez la table de recherche, avec l'option 1 (l'adressage des octets est sans surprise lent) si vous êtes préoccupé par les performances. Si vous devez extraire chaque dernier octet de mémoire de votre système (et vous pourriez, si vous vous souciez des performances de l'inversion de bits), les versions optimisées de l'approche bit à bit ET ne sont pas trop minables non plus.
Caveat
Oui, je sais que le code de référence est un hack complet. Les suggestions sur la façon de l'améliorer sont plus que bienvenues. Ce que je sais:
ld
explosé avec une erreur de redéfinition de symbole fou), donc je ne crois pas que le code généré est réglé pour ma microarchitecture.32 bits
EDIT: J'ai également essayé d'utiliser des
uint64_t
types sur ma machine pour voir s'il y avait une amélioration des performances. Les performances étaient environ 10% plus rapides que 32 bits et étaient presque identiques, que vous utilisiez simplement des types 64 bits pour inverser des bits sur deuxint
types 32 bits à la fois, ou que vous inversiez réellement des bits sur deux fois moins 64- valeurs binaires. Le code assembleur est illustré ci-dessous (pour le premier cas, inverser les bits pour deuxint
types 32 bits à la fois):la source
Ce fil a attiré mon attention car il traite d'un problème simple qui nécessite beaucoup de travail (cycles CPU) même pour un CPU moderne. Et un jour, je suis resté là avec le même problème ¤ #% "#". J'ai dû retourner des millions d'octets. Cependant, je sais que tous mes systèmes cibles sont basés sur des processeurs Intel modernes, alors commençons l'optimisation à l'extrême !!!
J'ai donc utilisé le code de recherche de Matt J comme base. le système sur lequel je compare est un i7 haswell 4700eq.
La recherche de Matt J bitflipping 400 000 000 octets: environ 0,272 secondes.
Je suis ensuite allé de l'avant et j'ai essayé de voir si le compilateur ISPC d'Intel pouvait vectoriser l'arithmétique dans le sens inverse.c.
Je ne vais pas vous ennuyer avec mes découvertes ici car j'ai beaucoup essayé pour aider le compilateur à trouver des trucs, de toute façon j'ai fini avec des performances d'environ 0,15 seconde pour bitflip 400 000 000 octets. C'est une grande réduction mais pour mon application c'est encore beaucoup trop lent ..
Donc, les gens me laissent présenter le bitflipper basé sur Intel le plus rapide au monde. Pointé à:
Temps de bitflip 400000000 octets: 0,050082 secondes !!!!!
Les printf sont pour le débogage ..
Voici le cheval de bataille:
Le code prend 32 octets puis masque les grignotages. Le quartet élevé est décalé à droite de 4. Ensuite, j'utilise vpshufb et ymm4 / ymm3 comme tables de recherche. Je pourrais utiliser une seule table de recherche, mais je devrais ensuite déplacer à gauche avant de réorganiser les grignotages.
Il existe des moyens encore plus rapides de retourner les bits. Mais je suis lié au thread unique et au processeur, donc c'était le plus rapide que j'ai pu atteindre. Pouvez-vous faire une version plus rapide?
Veuillez ne faire aucun commentaire sur l'utilisation des commandes équivalentes intrinsèques du compilateur Intel C / C ++ ...
la source
pshub
, car après tout, le meilleur popcount est également fait avec! Je l'aurais écrit ici sans toi. Gloire.popcnt
,tzcnt
etpext
tout sur le port 1. Ainsi, chaquepext
outzcnt
vous coûte unpopcnt
débit. Si vos données sont à chaud dans le cache L1D, le moyen le plus rapide de faire un tableau sur les processeurs Intel est avec pshufb AVX2. (Ryzen a unpopcnt
débit de 4 par horloge, ce qui est probablement optimal, mais la famille Bulldozer a unpopcnt r64,r64
débit par 4 horloges ... agner.org/optimize ).Ceci est une autre solution pour les gens qui aiment la récursivité.
L'idée est simple. Divisez l'entrée par moitié et échangez les deux moitiés, continuez jusqu'à ce qu'elle atteigne un seul bit.
Voici une fonction récursive pour le résoudre. (Notez que j'ai utilisé des entiers non signés, donc cela peut fonctionner pour des entrées jusqu'à sizeof (unsigned int) * 8 bits.
Voici la sortie:
la source
numBits
pour int, lorsque vous divisez 3 par 2 pour la fonction param, elle sera arrondie à 1?Eh bien, ce ne sera certainement pas une réponse comme celle de Matt J, mais j'espère qu'elle sera toujours utile.
C'est exactement la même idée que le meilleur algorithme de Matt, sauf qu'il y a cette petite instruction appelée BSWAP qui permute les octets (pas les bits) d'un nombre 64 bits. Donc b7, b6, b5, b4, b3, b2, b1, b0 devient b0, b1, b2, b3, b4, b5, b6, b7. Étant donné que nous travaillons avec un nombre 32 bits, nous devons réduire notre nombre à octets inversés de 32 bits. Cela nous laisse juste la tâche de permuter les 8 bits de chaque octet, ce qui est fait et le tour est joué! avaient fini.
Timing: sur ma machine, l'algorithme de Matt a fonctionné en ~ 0,52 seconde par essai. Le mien a fonctionné en environ 0,42 seconde par essai. 20% plus vite ce n'est pas mal je pense.
Si vous vous inquiétez de la disponibilité de l'instruction BSWAP Wikipedia répertorie l'instruction BSWAP comme étant ajoutée avec 80846 qui est sortie en 1989. Il convient de noter que Wikipedia indique également que cette instruction ne fonctionne que sur des registres 32 bits, ce qui n'est clairement pas le cas sur ma machine, il fonctionne très bien uniquement sur les registres 64 bits.
Cette méthode fonctionnera également bien pour tout type de données intégral, de sorte que la méthode peut être généralisée de manière triviale en passant le nombre d'octets souhaité:
qui peut ensuite être appelé comme:
Le compilateur devrait être en mesure d'optimiser le paramètre supplémentaire (en supposant que le compilateur intègre la fonction) et pour le
sizeof(size_t)
cas, le décalage vers la droite serait complètement supprimé. Notez que GCC au moins n'est pas en mesure de supprimer le BSWAP et le décalage à droite s'il est réussisizeof(char)
.la source
unsigned long long int
qui doivent être d'au moins 64 bits, comme ici et iciLa réponse d'Anders Cedronius fournit une excellente solution pour les personnes disposant d'un processeur x86 avec prise en charge AVX2. Pour les plates-formes x86 sans prise en charge AVX ou les plates-formes non x86, l'une des implémentations suivantes devrait fonctionner correctement.
Le premier code est une variante de la méthode de partitionnement binaire classique, codée pour maximiser l'utilisation de l'idiome shift-plus-logic utile sur divers processeurs ARM. En outre, il utilise la génération de masques à la volée qui pourrait être bénéfique pour les processeurs RISC qui, autrement, nécessitent plusieurs instructions pour charger chaque valeur de masque 32 bits. Les compilateurs pour les plates-formes x86 doivent utiliser une propagation constante pour calculer tous les masques au moment de la compilation plutôt qu'au moment de l'exécution.
Dans le volume 4A de "The Art of Computer Programming", D. Knuth montre des façons astucieuses d'inverser des bits qui, de façon surprenante, nécessitent moins d'opérations que les algorithmes de partitionnement binaire classiques. Un tel algorithme pour les opérandes 32 bits, que je ne trouve pas dans TAOCP, est présenté dans ce document sur le site Web de Hacker's Delight.
En utilisant le compilateur C / C ++ du compilateur Intel 13.1.3.198, les deux fonctions ci-dessus s'auto-vectorisent bien
XMM
registres de . Ils peuvent également être vectorisés manuellement sans beaucoup d'efforts.Sur mon IvyBridge Xeon E3 1270v2, en utilisant le code auto-vectorisé, 100 millions de
uint32_t
mots ont été inversés en 0,070 secondes en utilisantbrev_classic()
et 0,068 secondes en utilisantbrev_knuth()
. J'ai pris soin de m'assurer que mon benchmark n'était pas limité par la bande passante mémoire système.la source
brev_knuth()
? L'attribution dans le PDF de Hacker's Delight semble indiquer que ces chiffres proviennent directement de Knuth lui-même. Je ne peux pas prétendre avoir suffisamment compris la description de Knuth des principes de conception sous-jacents dans TAOCP pour expliquer comment les constantes ont été dérivées, ou comment on procéderait pour dériver les constantes et les facteurs de décalage pour des tailles de mots arbitraires.En supposant que vous avez un tableau de bits, que diriez-vous de cela: 1. À partir de MSB, poussez les bits dans une pile un par un. 2. Insérez les bits de cette pile dans un autre tableau (ou le même tableau si vous souhaitez économiser de l'espace), en plaçant le premier bit extrait dans MSB et en passant à des bits moins significatifs à partir de là.
la source
L'instruction native ARM "rbit" peut le faire avec 1 cycle de processeur et 1 registre de processeur supplémentaire, impossible à battre.
la source
Ce n'est pas un travail pour un humain! ... mais parfait pour une machine
Nous sommes en 2015, 6 ans après le début de la question. Les compilateurs sont depuis devenus nos maîtres, et notre travail en tant qu'humains n'est que de les aider. Alors, quelle est la meilleure façon de donner nos intentions à la machine?
L'inversion de bits est si courante que vous devez vous demander pourquoi l'ISA en constante augmentation du x86 n'inclut pas d'instructions pour le faire d'un seul coup.
La raison: si vous donnez votre véritable intention concise au compilateur, l'inversion de bits ne devrait prendre que ~ 20 cycles CPU . Permettez-moi de vous montrer comment créer reverse () et l'utiliser:
La compilation de cet exemple de programme avec la version Clang> = 3.6, -O3, -march = native (testé avec Haswell), donne un code de qualité graphique à l'aide des nouvelles instructions AVX2, avec un temps d'exécution de 11 secondes traitant ~ 1 milliard de reverse () s. C'est ~ 10 ns par reverse (), avec un cycle de processeur de 0,5 ns en supposant que 2 GHz nous place aux 20 cycles de processeur doux.
Avertissement: cet exemple de code devrait constituer une référence décente pendant quelques années, mais il commencera finalement à montrer son âge une fois que les compilateurs seront suffisamment intelligents pour optimiser main () afin d'imprimer simplement le résultat final au lieu de vraiment calculer quoi que ce soit. Mais pour l'instant, cela fonctionne en présentant reverse ().
la source
Bit-reversal is so common...
Je n'en sais rien. Je travaille avec du code qui traite des données au niveau du bit pratiquement tous les jours, et je ne me souviens pas avoir jamais eu ce besoin spécifique. Dans quels scénarios en avez-vous besoin? - Non pas que ce ne soit pas un problème intéressant à résoudre à part entière.Bien sûr, la source évidente de piratages de bits est ici: http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious
la source
Je sais que ce n'est pas C mais asm:
Cela fonctionne avec le bit de transport, vous pouvez donc également enregistrer des indicateurs
la source
rcl
déplacer CF dansvar1
, au lieu de simplementshl
ne pas lire les drapeaux. (Ouadc dx,dx
). Même avec ce correctif, c'est ridiculement lent, en utilisant l'loop
instruction lente et en gardantvar1
en mémoire! En fait, je pense que cela est censé produire la sortie dans AX, mais il enregistre / restaure l'ancienne valeur d'AX par-dessus le résultat.Implémentation avec peu de mémoire et plus rapide.
la source
Eh bien, c'est fondamentalement le même que le premier "reverse ()" mais il est de 64 bits et n'a besoin que d'un masque immédiat pour être chargé à partir du flux d'instructions. GCC crée du code sans sauts, donc cela devrait être assez rapide.
la source
J'étais curieux de voir à quelle vitesse serait la rotation brute évidente. Sur ma machine (i7 @ 2600), la moyenne pour 1 500 150 000 itérations était
27.28 ns
(sur un ensemble aléatoire de 131 071 entiers 64 bits).Avantages: la quantité de mémoire nécessaire est faible et le code est simple. Je dirais que ce n'est pas si grand non plus. Le temps requis est prévisible et constant pour toute entrée (128 opérations de décalage arithmétique + 64 opérations logiques ET + 64 opérations logiques OU).
J'ai comparé au meilleur temps obtenu par @Matt J - qui a la réponse acceptée. Si j'ai bien lu sa réponse, le meilleur qu'il a obtenu était de
0.631739
quelques secondes pour les1,000,000
itérations, ce qui conduit à une moyenne631 ns
par rotation.L'extrait de code que j'ai utilisé est celui-ci ci-dessous:
la source
Vous souhaiterez peut-être utiliser la bibliothèque de modèles standard. Il peut être plus lent que le code mentionné ci-dessus. Cependant, il me semble plus clair et plus facile à comprendre.
la source
Générique
Code C. En utilisant l'exemple de données d'entrée 1 octet.
la source
Qu'en est-il des éléments suivants:
Petit et facile (cependant, 32 bits uniquement).
la source
Je pensais que c'était l'un des moyens les plus simples d'inverser le bit. veuillez me faire savoir s'il y a un défaut dans cette logique. Fondamentalement, dans cette logique, nous vérifions la valeur du bit en position. mettre le bit si la valeur est 1 en position inversée.
la source
la source
k
est toujours une puissance de 2, mais les compilateurs ne le prouveront probablement pas et ne le transformeront pas en bit-scan / shift.Je pense que la méthode la plus simple que je connaisse suit.
MSB
est une entrée et uneLSB
sortie «inversée»:la source
la source
Une autre solution basée sur une boucle qui se ferme rapidement lorsque le nombre est faible (en C ++ pour plusieurs types)
ou en C pour un entier non signé
la source
Il semble que de nombreux autres articles se préoccupent de la vitesse (c'est-à-dire le meilleur = le plus rapide). Et la simplicité? Considérer:
et espérons que ce compilateur intelligent sera optimisé pour vous.
Si vous souhaitez inverser une liste de bits plus longue (contenant des
sizeof(char) * n
bits), vous pouvez utiliser cette fonction pour obtenir:Cela inverserait [10000000, 10101010] en [01010101, 00000001].
la source
ith_bit = (c >> i) & 1
. Enregistrez également un SUB en décalantreversed_char
au lieu de décaler le bit, sauf si vous espérez qu'il se compilera sur x86 poursub something
/bts reg,reg
pour définir le nième bit dans le registre de destination.Inversion de bits dans un pseudo-code
source -> octet à inverser b00101100 destination -> inversé, doit également être de type non signé pour que le bit de signe ne soit pas propagé vers le bas
la copie dans temp afin que l'original ne soit pas affecté, doit également être de type non signé pour que le bit de signe ne soit pas décalé automatiquement
LOOP8: // effectuez ce test 8 fois si la copie parallèle est <0 (négatif)
la source
Ma solution simple
la source
i
? Aussi, quelle est cette constante magique* 4
? C'est çaCHAR_BIT / 2
?C'est pour 32 bits, nous devons changer la taille si nous considérons 8 bits.
Lecture de l'entier d'entrée "num" dans l'ordre LSB-> MSB et stockage dans num_reverse dans l'ordre MSB-> LSB.
la source
la source