Si j'ai un entier n et que je veux connaître la position du bit le plus significatif (c'est-à-dire que si le bit le moins significatif est à droite, je veux connaître la position du bit le plus à gauche qui est un 1), Quelle est la méthode la plus rapide / la plus efficace pour le découvrir?
Je sais que POSIX prend en charge une ffs()
méthode dans strings.h pour trouver le premier bit défini, mais il ne semble pas y avoir de fls()
méthode correspondante .
Y a-t-il un moyen vraiment évident de faire cela qui me manque?
Qu'en est-il des cas où vous ne pouvez pas utiliser les fonctions POSIX pour la portabilité?
Edit: Qu'en est-il d'une solution qui fonctionne à la fois sur les architectures 32 et 64 bits (la plupart des listes de codes semblent ne fonctionner que sur les entiers 32 bits).
Réponses:
GCC a :
Je m'attendrais à ce qu'ils soient traduits en quelque chose d'assez efficace pour votre plate-forme actuelle, que ce soit l'un de ces algorithmes sophistiqués de twiddling ou une seule instruction.
Une astuce utile si votre entrée peut être égale à zéro est
__builtin_clz(x | 1)
: le réglage inconditionnel du bit bas sans en modifier les autres fait la sortie31
pourx=0
, sans changer la sortie pour une autre entrée.Pour éviter d'avoir à faire cela, votre autre option est les intrinsèques spécifiques à la plate-forme comme ARM GCC
__clz
(aucun en-tête nécessaire), ou x86_lzcnt_u32
sur les processeurs qui prennent en charge l'lzcnt
instruction. (Attention aulzcnt
décodage commebsr
sur les processeurs plus anciens au lieu de défaut, ce qui donne 31-lzcnt pour les entrées non nulles.)Il n'y a malheureusement aucun moyen de tirer parti des différentes instructions CLZ sur les plates-formes non x86 qui définissent le résultat pour input = 0 comme 32 ou 64 (selon la largeur de l'opérande). x86 le
lzcnt
fait aussi, tandis quebsr
produit un index de bits que le compilateur doit retourner à moins que vous n'utilisiez31-__builtin_clz(x)
.(Le "résultat non défini" n'est pas C Undefined Behavior, juste une valeur qui n'est pas définie. Il s'agit en fait de ce qui se trouvait dans le registre de destination lorsque l'instruction a été exécutée. AMD le documente, Intel ne le fait pas, mais les processeurs Intel implémentent ce comportement . Mais ce n'est pas ce qui était auparavant dans la variable C que vous assignez, ce n'est généralement pas ainsi que les choses fonctionnent lorsque gcc transforme C en asm. Voir aussi Pourquoi la rupture de la "dépendance de sortie" de LZCNT est-elle importante? )
la source
__builtin_ctz
overffs
, qui se compile en un BSF et un CMOV pour gérer le cas d'entrée à zéro. Sur les architectures sans implémentation suffisamment courte (par exemple, l'ancien ARM sans l'clz
instruction), gcc émet un appel à une fonction d'assistance libgcc.En supposant que vous êtes sur x86 et jeu pour un peu d'assembleur en ligne, Intel fournit une
BSR
instruction ("bit scan reverse"). C'est rapide sur certains x86 (microcodés sur d'autres). À partir du manuel:(Si vous êtes sur PowerPC, il existe une
cntlz
instruction similaire ("compter les zéros non significatifs").)Exemple de code pour gcc:
Voir aussi ce tutoriel d'assembleur en ligne , qui montre (section 9.4) qu'il est considérablement plus rapide que le code en boucle.
la source
Puisque 2 ^ N est un entier avec uniquement le Nième bit défini (1 << N), la recherche de la position (N) du bit le plus élevé correspond à l'entier log de base 2 de cet entier.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
Cet algorithme "évident" peut ne pas être transparent pour tout le monde, mais lorsque vous réalisez que le code se décale d'un bit vers la droite à plusieurs reprises jusqu'à ce que le bit le plus à gauche ait été décalé (notez que C traite toute valeur non nulle comme vraie) et renvoie le nombre des quarts de travail, c'est parfaitement logique. Cela signifie également qu'il fonctionne même lorsque plus d'un bit est défini - le résultat est toujours pour le bit le plus significatif.
Si vous faites défiler cette page vers le bas, il existe des variantes plus rapides et plus complexes. Cependant, si vous savez que vous avez affaire à des nombres avec beaucoup de zéros non significatifs, l'approche naïve peut fournir une vitesse acceptable, car le décalage de bits est plutôt rapide en C, et l'algorithme simple ne nécessite pas d'indexer un tableau.
REMARQUE: lorsque vous utilisez des valeurs 64 bits, soyez extrêmement prudent lorsque vous utilisez des algorithmes très intelligents; beaucoup d'entre eux ne fonctionnent correctement que pour les valeurs 32 bits.
la source
>>>
. Plus probablement le comparateur!= 0
, et un nombre non spécifié de parenthèses.Cela devrait être rapide comme l'éclair:
la source
C'est un peu comme trouver une sorte de journal d'entiers. Il y a des trucs à faire, mais j'ai créé mon propre outil pour cela. Le but est bien sûr la vitesse.
Ma réalisation est que le CPU a déjà un détecteur de bits automatique, utilisé pour la conversion d'entiers en flottants! Alors utilisez ça.
Cette version convertit la valeur en un double, puis lit l'exposant, qui vous indique où se trouvait le bit. Le décalage et la soustraction fantaisie consistent à extraire les parties appropriées de la valeur IEEE.
Il est légèrement plus rapide d'utiliser des flotteurs, mais un flotteur ne peut vous donner que les 24 premières positions de bits en raison de sa plus petite précision.
Pour ce faire en toute sécurité, sans comportement indéfini en C ++ ou C, utilisez
memcpy
plutôt que le cast de pointeur pour le poinçonnage de type. Les compilateurs savent comment l'intégrer efficacement.Ou dans C99 et versions ultérieures, utilisez un fichier
union {double d; uint32_t u[2];};
. Mais notez qu'en C ++, la punition de type union n'est prise en charge que sur certains compilateurs en tant qu'extension, pas dans ISO C ++.Cela sera généralement plus lent qu'un intrinsèque spécifique à la plate-forme pour une instruction de comptage de zéros en tête, mais l'ISO C portable n'a pas une telle fonction. Certains processeurs ne disposent pas non plus d'une instruction de comptage de début de zéro, mais certains d'entre eux peuvent convertir efficacement des entiers en
double
. Cependant, le poinçonnage d'un motif de bits FP en entier peut être lent (par exemple, sur PowerPC, il nécessite un stockage / rechargement et provoque généralement un blocage du magasin de chargement).Cet algorithme pourrait être utile pour les implémentations SIMD, car moins de processeurs ont SIMD
lzcnt
. x86 n'a reçu une telle instruction qu'avec AVX512CDla source
Kaz Kylheku ici
J'ai comparé deux approches pour ce nombre de plus de 63 bits (le type long long sur gcc x86_64), en évitant le bit de signe.
(Il se trouve que j'ai besoin de ce "trouver le bit le plus élevé" pour quelque chose, vous voyez.)
J'ai implémenté la recherche binaire basée sur les données (étroitement basée sur l'une des réponses ci-dessus). J'ai également implémenté à la main un arbre de décision complètement déroulé, qui n'est que du code avec des opérandes immédiats. Pas de boucles, pas de tables.
L'arbre de décision (most_bit_unrolled) est évalué à 69% plus rapide, sauf pour le cas n = 0 pour lequel la recherche binaire a un test explicite.
Le test spécial de la recherche binaire pour le cas 0 n'est que 48% plus rapide que l'arbre de décision, qui n'a pas de test spécial.
Compilateur, machine: (GCC 4.5.2, -O3, x86-64, 2867 Mhz Intel Core i5).
Programme de test rapide et sale:
En utilisant uniquement -O2, la différence devient plus grande. L'arbre de décision est presque quatre fois plus rapide.
J'ai également comparé le code de changement de bits naïf:
Ce n'est rapide que pour de petits nombres, comme on pouvait s'y attendre. En déterminant que le bit le plus élevé est 1 pour n == 1, il a évalué plus de 80% plus rapidement. Cependant, la moitié des nombres choisis au hasard dans l'espace de 63 bits ont le 63e bit défini!
Sur l'entrée 0x3FFFFFFFFFFFFFFF, la version de l'arbre de décision est un peu plus rapide qu'elle ne l'est sur 1 et se révèle être 1120% plus rapide (12,2 fois) que le décaleur de bits.
Je vais également comparer l'arbre de décision aux fonctions intégrées de GCC, et essayer également un mélange d'entrées plutôt que de répéter avec le même nombre. Il peut y avoir une prédiction de branche bloquée et peut-être des scénarios de mise en cache irréalistes qui le rendent artificiellement plus rapide sur les répétitions.
la source
Qu'en est-il de
?
la source
1 registre, 13 instructions. Croyez-le ou non, c'est généralement plus rapide que l'instruction BSR mentionnée ci-dessus, qui fonctionne en temps linéaire. C'est le temps logarithmique.
Depuis http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit
la source
__builtin_clz
s'il est activé avec-march=native
ou quelque chose (car il est rapide sur tous les processeurs qui le prennent en charge). Même sur des processeurs comme la famille AMD Bulldozer où BSR est "lent", ce n'est pas si lent: 7 m-ops avec une latence de 4 cycles et un par débit 4c. Sur Atom, BSR est vraiment lent: 16 cycles. Sur Silvermont, c'est 10 uops avec 10 cycles de latence. Cela pourrait être une latence légèrement inférieure à BSR sur Silvermont, mais IDK.Voici quelques (simples) benchmarks, des algorithmes actuellement donnés sur cette page ...
Les algorithmes n'ont pas été testés sur toutes les entrées de unsigned int; alors vérifiez d'abord cela, avant d'utiliser aveuglément quelque chose;)
Sur ma machine, clz (__builtin_clz) et asm fonctionnent le mieux. asm semble encore plus rapide que clz ... mais cela pourrait être dû au simple benchmark ...
la source
Bien que je n'utiliserais probablement cette méthode que si j'avais absolument besoin des meilleures performances possibles (par exemple pour écrire une sorte d'IA de jeu de société impliquant des bitboards), la solution la plus efficace est d'utiliser l'ASM en ligne. Consultez la section Optimisations de cet article de blog pour le code avec une explication.
la source
J'avais besoin d'une routine pour faire cela et avant de chercher sur le Web (et de trouver cette page), j'ai proposé ma propre solution basée sur une recherche binaire. Même si je suis sûr que quelqu'un a déjà fait ça! Il fonctionne en temps constant et peut être plus rapide que la solution "évidente" publiée, bien que je ne fasse pas de grandes déclarations, je la publie simplement par intérêt.
la source
c'est une sorte de recherche binaire, cela fonctionne avec toutes sortes de types d'entiers (non signés!)
pour faire complet:
la source
typedef
s ou quoi que ce soit sauf les macros de préprocesseur. C'est une convention largement acceptée.Quelques réponses trop complexes ici. La technique Debruin ne doit être utilisée que lorsque l'entrée est déjà une puissance de deux, sinon il existe un meilleur moyen. Pour une puissance de 2 entrées, Debruin est le plus rapide absolu, encore plus rapide que
_BitScanReverse
sur n'importe quel processeur que j'ai testé. Cependant, dans le cas général,_BitScanReverse
(ou quel que soit le nom intrinsèque de votre compilateur) est le plus rapide (sur certains processeurs, il peut cependant être microcodé).Si la fonction intrinsèque n'est pas une option, voici une solution logicielle optimale pour le traitement des entrées générales.
Notez que cette version ne nécessite pas de recherche Debruin à la fin, contrairement à la plupart des autres réponses. Il calcule la position en place.
Les tables peuvent être préférables cependant, si vous les appelez à plusieurs reprises, le risque de manque de cache est éclipsé par l'accélération d'une table.
Cela devrait produire le débit le plus élevé de toutes les réponses logicielles données ici, mais si vous ne l'appelez qu'occasionnellement, préférez une solution sans table comme mon premier extrait de code.
la source
Comme le soulignent les réponses ci-dessus, il existe plusieurs façons de déterminer le bit le plus significatif. Cependant, comme cela a également été souligné, les méthodes sont susceptibles d'être uniques aux registres 32 bits ou 64 bits. La page bithacks de stanford.edu fournit des solutions qui fonctionnent à la fois pour l'informatique 32 bits et 64 bits. Avec un peu de travail, ils peuvent être combinés pour fournir une solide approche cross-architecture pour obtenir le MSB. La solution à laquelle je suis arrivé et qui a été compilé / travaillé sur des ordinateurs 64 et 32 bits était:
la source
#ifdef BUILD_64
drapeau? Dans ce cas, il ne serait pas nécessaire de redéfinir le conditionnel.Une version en C utilisant des approximations successives:
Avantage: le temps de fonctionnement est constant quel que soit le nombre fourni, car le nombre de boucles est toujours le même. (4 boucles lors de l'utilisation de "unsigned int")
la source
msb += (n>>msb) ? step : -step;
), plus de compilateurs sont susceptibles de créer un asm sans branche, évitant ainsi les erreurs de prédiction de branche à chaque étape ( stackoverflow.com/questions/11227809/… ).Je sais que cette question est très ancienne, mais juste après avoir implémenté une fonction msb () moi-même, j'ai trouvé que la plupart des solutions présentées ici et sur d'autres sites Web ne sont pas nécessairement les plus efficaces - du moins pour ma définition personnelle de l'efficacité (voir aussi Mise à jour ci-dessous ). Voici pourquoi:
La plupart des solutions (en particulier celles qui utilisent une sorte de schéma de recherche binaire ou l'approche naïve qui effectue un balayage linéaire de droite à gauche) semblent négliger le fait que pour les nombres binaires arbitraires, il n'y en a pas beaucoup qui commencent par une très longue séquence de des zéros. En fait, pour toute largeur de bit, la moitié de tous les entiers commencent par 1 et un quart d'entre eux commencent par 01 . Voyez où j'en suis? Mon argument est qu'un balayage linéaire commençant de la position de bit la plus significative à la moins significative (de gauche à droite) n'est pas si "linéaire" qu'il pourrait le paraître à première vue.
On peut montrer 1 , que pour toute largeur de bit, le nombre moyen de bits à tester est d'au plus 2. Cela se traduit par une complexité en temps amorti de O (1) par rapport au nombre de bits (!) .
Bien sûr, le pire des cas est toujours O (n) , pire que le O (log (n)) que vous obtenez avec les approches de type recherche binaire, mais comme il y a si peu de pires cas, ils sont négligeables pour la plupart des applications ( Mise à jour : pas tout à fait: il peut y en avoir peu, mais ils peuvent se produire avec une probabilité élevée - voir Mise à jour ci-dessous).
Voici l'approche «naïve» que j'ai proposée, qui au moins sur ma machine bat la plupart des autres approches (les schémas de recherche binaire pour les entiers 32 bits nécessitent toujours log 2 (32) = 5 étapes, alors que cet algorithme stupide nécessite moins que 2 en moyenne) - désolé pour cela étant du C ++ et non du C pur:
Mise à jour : Bien que ce que j'ai écrit ici soit parfaitement vrai pour les entiers arbitraires , où chaque combinaison de bits est également probable (mon test de vitesse mesurait simplement le temps qu'il fallait pour déterminer le MSB pour tous les entiers 32 bits), des entiers réels, pour laquelle une telle fonction sera appelée, suivent généralement un modèle différent: dans mon code, par exemple, cette fonction est utilisée pour déterminer si une taille d'objet est une puissance de 2, ou pour trouver la prochaine puissance de 2 supérieure ou égale à un taille de l'objet . Je suppose que la plupart des applications utilisant le MSB impliquent des nombres beaucoup plus petits que le nombre maximum qu'un entier peut représenter (les tailles d'objet utilisent rarement tous les bits d'un size_t). Dans ce cas, ma solution sera en fait pire qu'une approche de recherche binaire - donc cette dernière devrait probablement être préférée, même si ma solution sera plus rapide en boucle sur tous les entiers.
TL; DR: Les entiers réels auront probablement un biais vers le pire des cas de cet algorithme simple, ce qui le rendra pire à la fin - malgré le fait qu'il est amorti O (1) pour des entiers vraiment arbitraires.
1 L'argument est le suivant (brouillon): Soit n le nombre de bits (largeur en bits). Il y a un total de 2 n entiers qui peuvent être représentés avec n bits. Il y a 2 n - 1 entiers commençant par 1 (le premier 1 est fixe, les n - 1 bits restants peuvent être n'importe quoi). Ces entiers ne nécessitent qu'une seule interation de la boucle pour déterminer le MSB. De plus, il y a 2 n - 2 entiers commençant par 01 , nécessitant 2 itérations, 2 n - 3 entiers commençant par 001 , nécessitant 3 itérations, et ainsi de suite.
Si nous additionnons toutes les itérations requises pour tous les entiers possibles et les divisons par 2 n , le nombre total d'entiers, nous obtenons le nombre moyen d'itérations nécessaires pour déterminer le MSB pour les entiers à n bits:
(1 * 2 n - 1 + 2 * 2 n - 2 + 3 * 2 n - 3 + ... + n) / 2 n
Cette série d'itérations moyennes est en fait convergente et a une limite de 2 pour n vers l'infini
Ainsi, l'algorithme naïf de gauche à droite a en fait une complexité temporelle constante amortie de O (1) pour n'importe quel nombre de bits.
la source
c99nous a donné
log2
. Cela supprime le besoin de toutes leslog2
implémentations spéciales de sauce que vous voyez sur cette page. Vous pouvez utiliser l'log2
implémentation de la norme comme ceci:Un
n
de0UL
doit également être protégé, car:Je l' ai écrit un exemple de ce contrôle que les ensembles arbitrairement
Index
àULONG_MAX
ici: https://ideone.com/u26vsile Visual Studiocorollaire à la seule réponse gcc d' Ephemient est:
La documentation pour les
_BitScanReverse
états quiIndex
est:Dans la pratique , j'ai trouvé que si
n
est0UL
queIndex
est réglé0UL
, tout comme il serait unn
des1UL
. Mais la seule chose garantie dans la documentation dans le cas d'unn
de0UL
est que le retour est:Ainsi, de manière similaire à l'
log2
implémentation préférable ci-dessus, le retour doit être vérifié en définissantIndex
une valeur marquée dans ce cas. J'ai à nouveau écrit un exemple d'utilisationULONG_MAX
de cette valeur d'indicateur ici: http://rextester.com/GCU61409la source
_BitScanReverse
renvoie 0 uniquement si l'entrée était0
. C'est comme l'BSR
instruction de x86 , qui définit ZF uniquement en fonction de l'entrée, pas de la sortie. Il est intéressant de noter que MS indique que les documents neindex
sont pas définis lorsqu'aucun1
bit n'est trouvé; qui correspond également au comportement asm x86 debsr
. (AMD le documente comme laissant le registre de destination non modifié sur src = 0, mais Intel dit simplement une sortie non définie même si leurs processeurs implémentent le comportement de non-modification.) Ceci est différent de x86lzcnt
, qui donne32
pour non-trouvé._BitScanReverse
utilise l'indexation de base zéro, donc sin
est 1 alors l'index du bit défini est en fait 0. Malheureusement, comme vous dites sin
est 0, la sortie est également 0 :( Cela signifie qu'il n'y a aucun moyen d'utiliser le retour à faire la distinction entren
1 et 0. C'est ce que j'essayais de communiquer. Pensez-vous qu'il existe une meilleure façon de dire cela?Index
. Ce n'est pas la valeur de retour . Il renvoie un booléen qui est faux si l'entrée était égale à zéro (et c'est pourquoi Index est passé par référence au lieu d'être renvoyé normalement). godbolt.org/g/gQKJdE . Et j'ai vérifié: malgré le libellé des documents de MS,_BitScanReverse
ne laisse pas Index désactivén==0
: vous obtenez juste la valeur du registre qu'il utilise. (Ce qui dans votre cas était probablement le même registre qu'il a utilisé par laIndex
suite, ce qui vous a amené à voir a0
).log2
depuis C99.Pensez aux opérateurs au niveau du bit.
J'ai mal compris la question la première fois. Vous devriez produire un int avec le bit le plus à gauche (les autres à zéro). En supposant que cmp est défini sur cette valeur:
la source
8
devrait êtreCHAR_BIT
. Il est très peu probable que ce soit le moyen le plus rapide, car une erreur de prédiction de branche se produira à la sortie de la boucle à moins que celle-ci ne soit utilisée à plusieurs reprises avec la même entrée. Aussi, pour les petites entrées (beaucoup de zéros), il doit beaucoup boucler. C'est comme la méthode de secours que vous utiliseriez comme version facile à vérifier dans un test unitaire pour comparer avec des versions optimisées.En se développant sur la référence de Josh ... on peut améliorer le clz comme suit
Concernant l'asm: notez qu'il existe bsr et bsrl (c'est la version "longue"). la normale pourrait être un peu plus rapide.
la source
Notez que ce que vous essayez de faire est de calculer le log2 entier d'un entier,
Notez que vous pouvez essayer de rechercher plus d'un bit à la fois.
Cette approche utilise une recherche binaire
Une autre méthode de recherche binaire, peut-être plus lisible,
Et parce que vous voudrez les tester,
la source
Mettre cela en place étant donné que c'est «encore une autre» approche, semble être différent des autres déjà donnés.
renvoie
-1
six==0
, sinonfloor( log2(x))
(résultat max 31)Réduisez le problème de 32 à 4 bits, puis utilisez une table. Peut-être inélégant, mais pragmatique.
C'est ce que j'utilise quand je ne veux pas utiliser
__builtin_clz
raison de problèmes de portabilité.Pour le rendre plus compact, on pourrait à la place utiliser une boucle pour réduire, en ajoutant 4 à r à chaque fois, max 7 itérations. Ou certains hybrides, comme (pour 64 bits): boucle pour réduire à 8, test pour réduire à 4.
la source
Woaw, c'était beaucoup de réponses. Je ne suis pas désolé d'avoir répondu à une vieille question.
Cette réponse est assez similaire à une autre réponse ... eh bien.
la source
1<<k
est une bonne idée. Et les masques?(1 << (1<<k-1)-1<< (1<<k-1)
? (most optimal
? Vous comparez un superlatif?)&
et&~
.) Vous pouvez remplacer les constantes hexadécimales par des valeurs similaires à((type)1<<(1<<k))-1<<(1<<k)
.Le code:
Ou obtenez la partie entière de l'instruction FPU FYL2X (Y * Log2 X) en définissant Y = 1
la source
double
, ce qui est probablement bien s'il stocke / recharge réellement au lieu du type-pun d'une autre manière, par exemple avec unemovq
instruction comme vous pourriez obtenir ici sur x86.[7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF]
.Une autre affiche a fourni une table de consultation utilisant une recherche octet-large . Au cas où vous voudriez gagner un peu plus de performances (au prix de 32 Ko de mémoire au lieu de seulement 256 entrées de recherche), voici une solution utilisant une table de recherche de 15 bits , en C # 7 pour .NET .
La partie intéressante est l'initialisation de la table. Comme il s'agit d'un bloc relativement petit que nous voulons pour la durée de vie du processus, j'alloue de la mémoire non gérée à cet effet en utilisant
Marshal.AllocHGlobal
. Comme vous pouvez le voir, pour des performances maximales, l'ensemble de l'exemple est écrit en natif:Le tableau nécessite une initialisation unique via le code ci-dessus. Il est en lecture seule, donc une seule copie globale peut être partagée pour un accès simultané. Avec ce tableau, vous pouvez rechercher rapidement le journal des nombres entiers 2 , ce que nous recherchons ici, pour toutes les différentes largeurs d'entiers (8, 16, 32 et 64 bits).
Notez que l'entrée de table pour
0
, le seul entier pour lequel la notion de 'bit le plus élevé' n'est pas définie, reçoit la valeur-1
. Cette distinction est nécessaire pour une gestion correcte des mots supérieurs de valeur 0 dans le code ci-dessous. Sans plus tarder, voici le code de chacune des différentes primitives entières:Version ulong (64 bits)
Version uint (32 bits)
Diverses surcharges pour ce qui précède
Il s'agit d'une solution complète et fonctionnelle qui représente les meilleures performances sur .NET 4.7.2 pour de nombreuses alternatives que j'ai comparées à un harnais de test de performances spécialisé. Certains d'entre eux sont mentionnés ci-dessous. Les paramètres de test étaient une densité uniforme de toutes les positions de 65 bits, c'est-à-dire 0 ... 31/63 plus la valeur
0
(qui produit le résultat -1). Les bits sous la position d'index cible ont été remplis de manière aléatoire. Les tests étaient uniquement x64 , en mode version, avec les optimisations JIT activées.C'est la fin de ma réponse formelle ici; ce qui suit sont quelques notes occasionnelles et des liens vers le code source pour les candidats aux tests alternatifs associés aux tests que j'ai exécutés pour valider les performances et l'exactitude du code ci-dessus.
La version fournie ci-dessus, codée Tab16A, a été un gagnant constant sur de nombreuses courses. Ces différents candidats, sous forme de travail actif / scratch, peuvent être trouvés ici , ici et ici .
Il est à noter que les terribles performances de
ntdll.dll!RtlFindMostSignificantBit
via P / Invoke:C'est vraiment dommage, car voici toute la fonction réelle:
Je ne peux pas imaginer les mauvaises performances provenant de ces cinq lignes, donc les pénalités de transition gérée / native doivent être à blâmer. J'ai également été surpris que les tests aient vraiment favorisé les
short
tables de recherche directe de 32 Ko (et 64 Ko) (16 bits) par rapport aux tables de recherche de 128 octets (et 256 octets)byte
(8 bits). Je pensais que ce qui suit serait plus compétitif avec les recherches 16 bits, mais ces dernières ont toujours surpassé ceci:La dernière chose que je ferai remarquer est que j'ai été assez choqué que ma méthode deBruijn n'ait pas mieux fonctionné. C'est la méthode que j'avais précédemment utilisée de manière généralisée:
Il y a beaucoup de discussions sur la façon dont les méthodes deBruijn supérieures et excellentes à cette question SO , et j'avais tendance à être d'accord. Ma spéculation est que, alors que les méthodes deBruijn et de table de recherche directe (que j'ai trouvées les plus rapides) doivent toutes deux faire une recherche de table, et ont toutes deux un branchement très minimal, seule la deBruijn a une opération de multiplication 64 bits. Je n'ai testé que les
IndexOfMSB
fonctions ici - pas le deBruijn -IndexOfLSB
mais je m'attends à ce que ce dernier ait beaucoup plus de chances car il a tellement moins d'opérations (voir ci-dessus), et je continuerai probablement à l'utiliser pour LSB.la source
Mon humble méthode est très simple:
MSB (x) = INT [Journal (x) / Journal (2)]
Traduction: Le MSB de x est la valeur entière de (Log of Base x divisé par le Log of Base 2).
Cela peut être facilement et rapidement adapté à n'importe quel langage de programmation. Essayez-le sur votre calculatrice pour voir par vous-même que cela fonctionne.
la source
int(math.log((1 << 48) - 1) / math.log(2))
est 48.Voici une solution rapide pour C qui fonctionne dans GCC et Clang ; prêt à être copié et collé.
Et une version légèrement améliorée pour C ++ .
Le code suppose que
value
ce ne sera pas le cas0
. Si vous souhaitez autoriser 0, vous devez le modifier.la source
Je suppose que votre question concerne un entier (appelé v ci-dessous) et non un entier non signé.
Si vous voulez le faire fonctionner sans prendre en compte le signe, vous pouvez ajouter un "v << = 1;" supplémentaire. avant la boucle (et changez la valeur de r à 30 en conséquence). Faites-moi savoir si j'ai oublié quelque chose. Je ne l'ai pas testé mais cela devrait fonctionner correctement.
la source
v <<= 1
est un comportement indéfini (UB) quandv < 0
.0x8000000
, peut-être voulez-vous dire un 0 supplémentaire.