Je travaille presque exclusivement en C ++ 11/14, et je grince généralement des dents quand je vois du code comme ceci:
std::int64_t mArray;
mArray |= someMask << 1;
C'est juste un exemple; Je parle de la manipulation au niveau du bit en général. En C ++, y a-t-il vraiment un point? Ce qui précède est déformant et sujet aux erreurs, tandis que l'utilisation d'un std::bitset
vous permet de:
- modifier plus facilement la taille de la
std::bitset
au besoin en ajustant un paramètre de modèle et en laissant l'implémentation s'occuper du reste, et - passez moins de temps à comprendre ce qui se passe (et peut-être à faire des erreurs) et à écrire
std::bitset
de manière similaire à d'std::array
autres conteneurs de données.
Ma question est; y a-t-il une raison de ne pas utiliser std::bitset
des types primitifs, autres que pour la compatibilité descendante?
c++
c++11
bitwise-operators
quant
la source
la source
std::bitset
est fixée au moment de la compilation. C'est le seul inconvénient paralysant auquel je peux penser.std::bitset
manipulation des bits de type c (par exempleint
), qui est également corrigée au moment de la compilation.std::bitset
n'était pas disponible (ou connu de l'auteur) et il n'y avait aucune raison de réécrire le code à utiliserstd::bitset
.bitset
un, mais un petit vecteur ou un ensemble deint
s (d'index de bits) peut également être légitime. La philosophie de C / C ++ ne cache pas ces complexités de choix au programmeur.Réponses:
D'un point de vue logique (non technique), il n'y a aucun avantage.
Tout code C / C ++ ordinaire peut être encapsulé dans une "construction de bibliothèque" appropriée. Après un tel emballage, la question de "si cela est plus avantageux que cela" devient une question théorique.
Du point de vue de la vitesse, C / C ++ devrait permettre à la construction de la bibliothèque de générer du code aussi efficace que le code ordinaire qu'il encapsule. Ceci est cependant soumis à:
En utilisant ce type d'argument non technique, toutes les "fonctions manquantes" pourraient être ajoutées par n'importe qui, et ne sont donc pas considérées comme un inconvénient.
Cependant, les exigences et limitations intégrées ne peuvent pas être surmontées avec du code supplémentaire. Ci-dessous, je soutiens que la taille de
std::bitset
est une constante au moment de la compilation et que, même si elle n'est pas considérée comme un inconvénient, c'est toujours quelque chose qui affecte le choix de l'utilisateur.D'un point de vue esthétique (lisibilité, facilité d'entretien, etc.), il y a une différence.
Cependant, il n'est pas évident que le
std::bitset
code l'emporte immédiatement sur le code C ordinaire. Il faut regarder de plus gros morceaux de code (et non un échantillon de jouet) pour dire si l'utilisation destd::bitset
a amélioré la qualité humaine du code source.La vitesse de manipulation des bits dépend du style de codage. Le style de codage affecte à la fois la manipulation des bits C / C ++ et s'applique également à
std::bitset
, comme expliqué ci-dessous.Si l'on écrit du code qui utilise le
operator []
pour lire et écrire un bit à la fois, il faudra le faire plusieurs fois s'il y a plus d'un bit à manipuler. La même chose peut être dite du code de style C.Cependant,
bitset
a aussi d' autres opérateurs, tels queoperator &=
,operator <<=
, etc., qui fonctionne sur toute la largeur de la bitset. Étant donné que la machine sous-jacente peut souvent fonctionner sur 32 bits, 64 bits et parfois 128 bits (avec SIMD) à la fois (dans le même nombre de cycles de processeur), un code conçu pour tirer parti de ces opérations multi-bits peut être plus rapide que le code de manipulation de bits "en boucle".L'idée générale est appelée SWAR (SIMD dans un registre) , et est un sous-sujet sous manipulations de bits.
Certains fournisseurs C ++ peuvent implémenter
bitset
entre 64 bits et 128 bits avec SIMD. Certains fournisseurs pourraient ne pas (mais pourraient éventuellement le faire). S'il est nécessaire de savoir ce que fait la bibliothèque du fournisseur C ++, la seule façon est de regarder le désassemblage.Quant à savoir si
std::bitset
a des limites, je peux donner deux exemples.std::bitset
doit être connue au moment de la compilation. Pour faire un tableau de bits avec une taille choisie dynamiquement, il faudra utiliserstd::vector<bool>
.std::bitset
ne fournit pas un moyen d'extraire une tranche consécutive de N bits à partir d'un plus grandbitset
de M bits.La première est fondamentale, ce qui signifie que pour les personnes qui ont besoin de bitsets de taille dynamique, elles doivent choisir d'autres options.
Le second peut être surmonté, car on peut écrire une sorte d'adaptateurs pour effectuer la tâche, même si la norme
bitset
n'est pas extensible.Il existe certains types d'opérations SWAR avancées qui ne sont pas fournis dès le départ
std::bitset
. On pourrait lire sur ces opérations sur ce site Web les permutations de bits . Comme d'habitude, on peut les implémenter par eux-mêmes, fonctionnant par dessusstd::bitset
.Concernant la discussion sur la performance.
Un avertissement: beaucoup de gens demandent pourquoi (quelque chose) de la bibliothèque standard est beaucoup plus lent qu'un simple code de style C. Je ne répéterais pas ici les connaissances préalables en matière de micro-analyse comparative, mais j'ai juste ce conseil: assurez-vous de comparer en "mode de sortie" (avec les optimisations activées), et assurez-vous que le code n'est pas éliminé (élimination du code mort) ou hissé hors d'une boucle (mouvement de code invariant en boucle) .
Étant donné qu'en général, nous ne pouvons pas dire si quelqu'un (sur Internet) faisait correctement les microbenchmarks, la seule façon de parvenir à une conclusion fiable est de faire nos propres microbenchmarks, de documenter les détails et de soumettre à l'examen et à la critique du public. Cela ne fait pas de mal de refaire des microbenchmarks que d'autres ont déjà fait.
la source
std::bitset
. Il n'y a pas de garantie de cohérence de la mémoire (instd::bitset
), ce qui signifie qu'elle n'est pas censée être partagée entre les cœurs. Les personnes qui ont besoin de le partager entre les cœurs auront tendance à créer leur propre implémentation. Lorsque les données sont partagées entre différents cœurs, il est habituel de les aligner sur la limite de la ligne de cache. Ne pas le faire réduit les performances et expose davantage les pièges de non-atomicité. Je n'ai pas suffisamment de connaissances pour donner un aperçu de la manière de construire une implémentation parallélisable destd::bitset
.bitset
volonté.Cela ne s'applique certainement pas dans tous les cas, mais parfois un algorithme peut dépendre de l'efficacité du bit-twiddling de style C pour fournir des gains de performances significatifs. Le premier exemple qui me vient à l'esprit est l'utilisation de bitboards , d'encodages intelligents entiers de positions de jeux de société, pour accélérer les moteurs d'échecs et autres. Ici, la taille fixe des types entiers ne pose aucun problème, car les échiquiers sont toujours 8 * 8 de toute façon.
Pour un exemple simple, considérons la fonction suivante (tirée de cette réponse de Ben Jackson ) qui teste une position Connect Four pour la victoire:
la source
std::bitset
serait plus lent?