41 votes

Un bool d'un octet. Pourquoi ?

En C++, pourquoi un bool nécessite-t-il un octet pour stocker true ou false alors qu'un seul bit suffit pour cela, comme 0 pour false et 1 pour true ? (Pourquoi Java requiert-il également un octet ?)

Deuxièmement, dans quelle mesure est-il plus sûr d'utiliser ce qui suit ?

struct Bool {
    bool trueOrFalse : 1;
};

Troisièmement, même si elle est sûre, la technique de terrain ci-dessus est-elle vraiment utile ? J'ai entendu dire que nous gagnons de l'espace à cet endroit, mais le code généré par le compilateur pour y accéder est plus gros et plus lent que le code généré pour accéder aux primitives.

90voto

Oli Charlesworth Points 148744

Pourquoi un bool nécessite-t-il un octet pour stocker le vrai ou le faux alors qu'un seul bit suffit.

Parce que chaque objet en C++ doit être adressable individuellement. * (c'est-à-dire que vous devez être en mesure d'avoir un pointeur sur lui). Vous ne pouvez pas adresser un bit individuel (du moins pas sur le matériel conventionnel).

Dans quelle mesure est-il plus sûr d'utiliser les éléments suivants ?

C'est "sûr", mais ça ne donne pas grand-chose.

La technique de terrain ci-dessus est-elle vraiment utile ?

Non, pour les mêmes raisons que ci-dessus ;)

mais le code généré par le compilateur pour y accéder est toujours plus gros et plus lent que le code généré pour accéder aux primitives.

Oui, c'est vrai. Sur la plupart des plates-formes, cela nécessite d'accéder à l'octet contenant (ou int ou autre), puis d'effectuer des décalages de bits et des opérations de masque de bits pour accéder au bit concerné.


Si vous êtes vraiment préoccupé par l'utilisation de la mémoire, vous pouvez utiliser un fichier de type std::bitset en C++ ou un BitSet en Java, qui emballent des bits.


* A quelques exceptions près.

11voto

Peter Lawrey Points 229686

L'utilisation d'un seul bit est beaucoup plus lente et beaucoup plus compliquée à allouer. En C/C++, il n'y a aucun moyen d'obtenir l'adresse d'un bit, donc vous ne pourriez pas faire &trueOrFalse comme un bit.

Java possède un BitSet et un EnumSet qui utilisent tous deux des bitmaps. Si vous avez un très petit nombre d'objets, cela peut ne pas faire de différence. Par exemple, les objets doivent être alignés sur au moins un octet et dans HotSpot, ils sont alignés sur 8 octets (en C++, un octet est un octet). new Les objets peuvent être alignés sur 8 ou 16 octets. Cela signifie que le fait d'économiser quelques bits peut ne pas faire gagner d'espace.

En Java au moins, les bits ne sont pas plus rapides à moins qu'ils ne tiennent mieux dans le cache.

public static void main(String... ignored) {
    BitSet bits = new BitSet(4000);
    byte[] bytes = new byte[4000];
    short[] shorts = new short[4000];
    int[] ints = new int[4000];

    for (int i = 0; i < 100; i++) {
        long bitTime = timeFlip(bits) + timeFlip(bits);
        long bytesTime = timeFlip(bytes) + timeFlip(bytes);
        long shortsTime = timeFlip(shorts) + timeFlip(shorts);
        long intsTime = timeFlip(ints) + timeFlip(ints);
        System.out.printf("Flip time bits %.1f ns, bytes %.1f, shorts %.1f, ints %.1f%n",
                bitTime / 2.0 / bits.size(), bytesTime / 2.0 / bytes.length,
                shortsTime / 2.0 / shorts.length, intsTime / 2.0 / ints.length);
    }
}

private static long timeFlip(BitSet bits) {
    long start = System.nanoTime();
    for (int i = 0, len = bits.size(); i < len; i++)
        bits.flip(i);
    return System.nanoTime() - start;
}

private static long timeFlip(short[] shorts) {
    long start = System.nanoTime();
    for (int i = 0, len = shorts.length; i < len; i++)
        shorts[i] ^= 1;
    return System.nanoTime() - start;
}

private static long timeFlip(byte[] bytes) {
    long start = System.nanoTime();
    for (int i = 0, len = bytes.length; i < len; i++)
        bytes[i] ^= 1;
    return System.nanoTime() - start;
}

private static long timeFlip(int[] ints) {
    long start = System.nanoTime();
    for (int i = 0, len = ints.length; i < len; i++)
        ints[i] ^= 1;
    return System.nanoTime() - start;
}

imprime

Flip time bits 5.0 ns, bytes 0.6, shorts 0.6, ints 0.6

pour les tailles de 40000 et 400K

Flip time bits 6.2 ns, bytes 0.7, shorts 0.8, ints 1.1

pour 4M

Flip time bits 4.1 ns, bytes 0.5, shorts 1.0, ints 2.3

et 40M

Flip time bits 6.2 ns, bytes 0.7, shorts 1.1, ints 2.4

6voto

Philipp Claßen Points 4863

Si vous souhaitez ne stocker qu'un seul bit d'information, il n'y a rien de plus compact qu'un fichier de type char qui est la plus petite unité de mémoire adressable en C/C++. (Selon l'implémentation, un bool peut avoir la même taille qu'un char mais c'est autorisé à être plus grand .)

A char est garanti par la norme C pour contenir au moins 8 bits, mais il peut aussi en contenir davantage. Le nombre exact est disponible via la fonction CHAR_BIT définie dans limits.h (en C) ou climits (C++). Aujourd'hui, il est plus courant que CHAR_BIT == 8 mais vous ne pouvez pas vous y fier (voir ici ). Il est toutefois garanti qu'il est égal à 8 sur les systèmes compatibles POSIX et sur Windows .

Bien qu'il ne soit pas possible de réduire l'empreinte mémoire d'un seul drapeau, il est bien sûr possible de combiner plusieurs drapeaux. En plus de faire tout opérations de bit manuellement il existe des alternatives :

  • Si vous connaissez le nombre de bits au moment de la compilation
    1. champs de bits (comme dans votre question). Mais attention, l'ordre des champs n'est pas garanti, ce qui peut entraîner des problèmes de portabilité.
    2. std::bitset
  • Si vous ne connaissez la taille qu'au moment de l'exécution
    1. boost::dynamic_bitset
    2. Si vous devez traiter des vecteurs binaires de grande taille, jetez un coup d'œil à l'application Bibliothèque BitMagic . Il supporte la compression et est fortement optimisé.

Comme d'autres l'ont déjà souligné, il n'est pas toujours bon d'économiser quelques pièces. Les inconvénients possibles sont :

  1. Un code moins lisible
  2. Réduction de la vitesse d'exécution en raison du code d'extraction supplémentaire.
  3. Pour la même raison, l'augmentation de la taille du code, qui peut l'emporter sur les économies réalisées en matière de consommation de données.
  4. Problèmes de synchronisation cachés dans les programmes multithreads. Par exemple, le basculement de deux bits différents par deux threads différents peut entraîner une condition de course. En revanche, il est toujours sûr pour deux threads de modifier deux objets différents de types primitifs (par ex, char ).

En règle générale, elle est utile lorsque vous traitez des données volumineuses, car elle permet de réduire la pression sur la mémoire et le cache.

5voto

Jeremy Trifilo Points 131

Pourquoi ne pas simplement stocker l'état dans un octet ? Je n'ai pas encore testé ce qui suit, mais cela devrait vous donner une idée. Vous pouvez même utiliser un short ou un int pour 16 ou 32 états. Je crois que j'ai aussi un exemple fonctionnel en JAVA. Je le posterai quand je le trouverai.

__int8 state = 0x0;

bool getState(int bit)
{
  return (state & (1 << bit)) != 0x0;
}

void setAllOnline(bool online)
{
  state = -online;
}

void reverseState(int bit)
{
   state ^= (1 << bit);
}

Très bien, voici la version JAVA. Je l'ai stocké dans une valeur Int depuis. Si je me souviens bien, même l'utilisation d'un octet utiliserait 4 octets de toute façon. Et cela ne peut évidemment pas être utilisé comme un tableau.

public class State
{
    private int STATE;

    public State() { 
        STATE = 0x0;
    }

    public State(int previous) { 
        STATE = previous;
    }

   /*
    * @Usage - Used along side the #setMultiple(int, boolean);
    * @Returns the value of a single bit.
    */
    public static int valueOf(int bit)
    {
        return 1 << bit;
    }

   /*
    * @Usage - Used along side the #setMultiple(int, boolean);
    * @Returns the value of an array of bits.
    */
    public static int valueOf(int... bits)
    {
        int value = 0x0;
        for (int bit : bits)
            value |= (1 << bit);
        return value;
    }

   /*
    * @Returns the value currently stored or the values of all 32 bits.
    */
    public int getValue() 
    {
        return STATE;
    }

   /*
    * @Usage - Turns all bits online or offline.
    * @Return - <TRUE> if all states are online. Otherwise <FALSE>.
    */
    public boolean setAll(boolean online)
    {
        STATE = online ? -1 : 0;
        return online;
    }

   /*
    * @Usage - sets multiple bits at once to a specific state.
    * @Warning - DO NOT SET BITS TO THIS! Use setMultiple(State.valueOf(#), boolean);
    * @Return - <TRUE> if states were set to online. Otherwise <FALSE>.
    */
    public boolean setMultiple(int value, boolean online)
    {
        STATE |= value;
        if (!online)
            STATE ^= value;
        return online;
    }

   /*
    * @Usage - sets a single bit to a specific state.
    * @Return - <TRUE> if this bit was set to online. Otherwise <FALSE>.
    */
    public boolean set(int bit, boolean online)
    {
        STATE |= (1 << bit);
        if(!online)
            STATE ^= (1 << bit);
        return online;
    }

   /*
    * @return = the new current state of this bit.
    * @Usage = Good for situations that are reversed.
    */
    public boolean reverse(int bit)
    {
        return (STATE ^= (1 << bit)) == (1 << bit);
    }

   /*
    * @return = <TRUE> if this bit is online. Otherwise <FALSE>.
    */
    public boolean online(int bit)
    {
        int value = 1 << bit;
        return (STATE & value) == value;        
    }

   /*
    * @return = a String contains full debug information.
    */
    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("TOTAL VALUE: ");
        sb.append(STATE);
        for (int i = 0; i < 0x20; i++)
        {
            sb.append("\nState(");
            sb.append(i);
            sb.append("): ");
            sb.append(online(i));
            sb.append(", ValueOf: ");
            sb.append(State.valueOf(i));
        }
        return sb.toString();
    }
}

Je dois également souligner que vous ne devriez pas vraiment utiliser une classe spéciale pour cela, mais simplement avoir la variable stockée dans la classe qui sera la plus susceptible de l'utiliser. Si vous prévoyez d'avoir des centaines ou même des milliers de valeurs booléennes, pensez à un tableau d'octets.

Par exemple, l'exemple ci-dessous.

boolean[] states = new boolean[4096];

peut être convertie en la formule ci-dessous.

int[] states = new int[128];

Maintenant, vous vous demandez probablement comment accéder à l'index 4095 à partir d'un tableau de 128. Donc ce que cela fait, c'est que si nous le simplifions. Le 4095 est décalé de 5 bits vers la droite, ce qui est techniquement la même chose que de diviser par 32. Donc 4095 / 32 = arrondi vers le bas (127). Nous sommes donc à l'index 127 du tableau. Ensuite, nous exécutons 4095 & 31, ce qui lui donnera une valeur comprise entre 0 et 31. Cela ne fonctionnera qu'avec les puissances de deux moins 1. Par exemple, 0,1,3,7,15,31,63,127,255,511,1023, etc...

Donc maintenant nous pouvons accéder au bit à cette position. Comme vous pouvez le voir, c'est très très compact et c'est mieux que d'avoir 4096 booléens dans un fichier :) Cela permettra également une lecture/écriture beaucoup plus rapide d'un fichier binaire. Je n'ai aucune idée de ce qu'est ce truc de BitSet, mais ça ressemble à un déchet complet et puisque byte, short, int, long sont déjà sous leurs formes binaires, techniquement, vous pourriez aussi bien les utiliser tels quels. Ensuite, il faut créer une classe complexe pour accéder aux bits individuels de la mémoire, c'est ce que j'ai pu comprendre en lisant quelques messages.

boolean getState(int index)
{
    return (states[index >> 5] & 1 << (index & 0x1F)) != 0x0;
}

Plus d'informations...

Si ce qui précède est un peu confus, voici une version simplifiée de ce qui se passe.

Les types " octet ", " court ", " int ", " long "sont tous des types de données qui ont des plages différentes.

Vous pouvez consulter ce lien : http://msdn.microsoft.com/en-us/library/s3f49ktz(v=vs.80).aspx

Pour voir les plages de données de chacun.

Un octet est donc égal à 8 bits. Ainsi, un int qui est de 4 octets sera de 32 bits.

Maintenant, il n'y a pas de moyen facile d'apporter une valeur à l'élément N puissance. Cependant, grâce au décalage des bits, nous pouvons le simuler quelque peu. En effectuant 1 << N cela équivaut à 1 * 2^N. Donc si nous faisions 2 << 2^N, nous ferions 2 * 2^N. Donc pour effectuer des puissances de deux, il faut toujours faire "1 << N".

Nous savons maintenant qu'un int aura 32 bits et que nous pouvons utiliser chaque bit pour simplement les indexer.

Pour simplifier les choses, considérez l'opérateur "&" comme un moyen de vérifier si une valeur contient les bits d'une autre valeur. Disons que nous avons une valeur de 31. Pour arriver à 31, nous devons ajouter les bits suivants de 0 à 4. Qui sont 1,2,4,8, et 16. La somme de tous ces bits est égale à 31. Maintenant, lorsque nous exécutons 31 & 16, cela renvoie 16 parce que le bit 4, qui est 2^4 = 16. est situé dans cette valeur. Maintenant, disons que nous avons exécuté 31 & 20 qui vérifie si les bits 2 et 4 sont situés dans cette valeur. Cela retournera 20 puisque les deux bits 2 et 4 sont situés ici 2^2 = 4 + 2^4 = 16 = 20. Maintenant disons que nous avons fait 31 & 48. Cela vérifie les bits 4 et 5. Eh bien, nous n'avons pas le bit 5 dans 31. Donc cela ne retournera que 16. Il ne retournera pas 0. Ainsi, lorsque vous effectuez des contrôles multiples, vous devez vérifier que la valeur est physiquement égale à cette valeur. Au lieu de vérifier si elle est égale à 0.

Le programme ci-dessous vérifie si un bit individuel est à 0 ou à 1. 0 étant faux, et 1 étant vrai.

bool getState(int bit)
{
    return (state & (1 << bit)) != 0x0;
}

L'exemple ci-dessous permet de vérifier si deux valeurs contiennent ces bits. Pensez-y comme si chaque bit était représenté par 2^BIT, donc lorsque nous faisons

Je vais passer rapidement en revue certains des opérateurs. Nous venons d'expliquer légèrement l'opérateur "&". Maintenant, l'opérateur "|".

Lorsque vous effectuez les opérations suivantes

int value = 31;
value |= 16;
value |= 16;
value |= 16;
value |= 16;

La valeur sera toujours de 31. C'est parce que le bit 4 ou 2^4=16 est déjà activé ou mis à 1. Donc l'exécution de "|" retourne cette valeur avec ce bit activé. S'il est déjà activé, aucun changement n'est effectué. Nous utilisons "|=" pour donner à la variable la valeur retournée.

Au lieu de faire -> "valeur = valeur | 16 ;". On fait juste "valeur |= 16 ;".

Voyons maintenant un peu plus en détail comment le " & " et " | " peut être utilisé.

/*
 * This contains bits 0,1,2,3,4,8,9 turned on.
 */
const int CHECK = 1 | 2 | 4 | 8 | 16 | 256 | 512;

/*
 * This is some value were we add bits 0 through 9, but we skip 0 and 8.
 */
int value = 2 | 4 | 8 | 16 | 32 | 64 | 128 | 512;

Ainsi, lorsque nous exécutons le code ci-dessous.

int return_code = value & CHECK;

Le code de retour sera 2 + 4 + 8 + 16 + 512 = 542.

Donc, nous vérifiions 799, mais nous avons reçu 542. C'est parce que les bits o et 8 sont hors ligne ; ils sont égaux à 256 + 1 = 257 et 799 - 257 = 542.

Ce qui précède est un excellent moyen de vérifier si, par exemple, nous créons un jeu vidéo et que nous voulons vérifier si tel ou tel bouton a été enfoncé, si l'un d'entre eux l'a été. Nous pourrions simplement vérifier chacun de ces bits avec une seule vérification et ce serait beaucoup plus efficace que d'effectuer une vérification booléenne sur chaque état.

Disons maintenant que nous avons une valeur booléenne qui est toujours inversée.

Normalement, vous devriez faire quelque chose comme

bool state = false;

state = !state;

Il est possible de le faire avec des bits en utilisant le " ^ " opérateur.

Tout comme nous avons effectué "1 << N" pour choisir la valeur entière de ce bit. Nous pouvons faire la même chose avec l'inverse. Donc, tout comme nous avons montré comment "|=" stocke le retour, nous allons faire la même chose avec "^=". Donc ce que cela fait, c'est que si ce bit est activé, nous le désactivons. S'il est désactivé, nous l'activons.

void reverseState(int bit)
{
   state ^= (1 << bit);
}

Vous pouvez même lui faire renvoyer l'état actuel. Si vous voulez qu'il renvoie l'état précédent, il suffit de remplacer "!=" par "==". Donc, cette fonction effectue l'inversion puis vérifie l'état actuel.

bool reverseAndGet(int bit)
{
    return ((state ^= (1 << bit)) & (1 << bit)) != 0x0;
}

Il est également possible de stocker plusieurs valeurs non monobit (bool) dans un int. Disons que nous écrivons normalement notre position de coordonnées comme ci-dessous.

int posX = 0;
int posY = 0;
int posZ = 0;

Maintenant, disons que ceux-ci ne sont jamais passés 1023. Donc, de 0 à 1023, c'était la distance maximale pour tous ces cas. Je choisis 1023 pour d'autres raisons, comme mentionné précédemment, vous pouvez manipuler la variable "&" comme un moyen de forcer une valeur entre 0 et 2^N - 1 valeurs. Donc disons que votre plage est de 0 à 1023. Nous pouvons exécuter "valeur & 1023" et ce sera toujours une valeur entre 0 et 1023 sans aucune vérification des paramètres d'index. Gardez à l'esprit que, comme mentionné précédemment, cela ne fonctionne qu'avec les puissances de deux moins un. 2^10 = 1024 - 1 = 1023.

Par exemple, plus de if (valeur >= 0 && valeur <= 1023).

Donc 2^10 = 1024, ce qui nécessite 10 bits pour contenir un nombre entre 0 et 1023.

Donc 10x3 = 30, ce qui est toujours inférieur ou égal à 32. est suffisant pour contenir toutes ces valeurs dans un int.

Nous pouvons donc effectuer ce qui suit. Donc pour voir combien de bits nous avons utilisé. On fait 0 + 10 + 20. La raison pour laquelle j'ai mis le 0 ici est pour vous montrer visuellement que 2^0 = 1 donc # * 1 = #. La raison pour laquelle nous avons besoin de y << 10 est que x utilise 10 bits qui vont de 0 à 1023. Nous devons donc multiplier y par 1024 pour avoir des valeurs uniques pour chacun. Ensuite, Z doit être multiplié par 2^20, ce qui fait 1 048 576.

int position = (x << 0) | (y << 10) | (z << 20);

Les comparaisons sont ainsi rapides.

Nous pouvons maintenant faire

return this.position == position;

par rapport à

return this.x == x && this.y == y && this.z == z;

Et si nous voulions les positions réelles de chacun ?

Pour le x, nous faisons simplement ce qui suit.

int getX()
{ 
   return position & 1023;
}

Ensuite, pour le y, nous devons effectuer un décalage de bit à gauche puis un ET.

int getY()
{ 
   return (position >> 10) & 1023;
}

Comme vous pouvez le deviner, le Z est le même que le Y, mais au lieu de 10, nous utilisons 20.

int getZ()
{ 
   return (position >> 20) & 1023;
}

J'espère que les personnes qui liront ce document y trouveront des informations utiles :).

4voto

Kevin Points 1017

Si vous voulez vraiment utiliser 1 bit, vous pouvez utiliser un char pour stocker 8 booléens, et bitshift pour obtenir la valeur de celui que vous voulez. Je doute que cela soit plus rapide, et cela va probablement vous donner beaucoup de maux de tête de travailler de cette façon, mais techniquement c'est possible.

Par ailleurs, une tentative comme celle-ci pourrait s'avérer utile pour les systèmes qui n'ont pas beaucoup de mémoire disponible pour les variables, mais qui ont une puissance de traitement supérieure à ce dont vous avez besoin. Je doute fort que vous en ayez un jour besoin.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X