web-dev-qa-db-fra.com

Est-ce une bonne pratique d'utiliser des types de données plus petits pour les variables afin d'économiser de la mémoire?

Lorsque j'ai appris le langage C++ pour la première fois, j'ai appris qu'en plus de int, float, etc., des versions plus ou moins grandes de ces types de données existaient dans le langage. Par exemple, je pourrais appeler une variable x

int x;
or 
short int x;

La principale différence étant que short int prend 2 octets de mémoire tandis que int prend 4 octets, et short int a une valeur moindre, mais nous pourrions également appeler cela pour le rendre encore plus petit:

int x;
short int x;
unsigned short int x;

ce qui est encore plus restrictif.

Ma question ici est de savoir si c'est une bonne pratique d'utiliser des types de données distincts en fonction des valeurs que votre variable prend dans le programme. Est-ce une bonne idée de toujours déclarer des variables en fonction de ces types de données?

32
Bugster

La plupart du temps, le coût de l'espace est négligeable et vous ne devriez pas vous en soucier, mais vous devez vous soucier des informations supplémentaires que vous donnez en déclarant un type. Par exemple, si vous:

unsigned int salary;

Vous donnez une information utile à un autre développeur: le salaire ne peut pas être négatif.

La différence entre court, int, long va rarement causer des problèmes d'espace dans votre application. Il est plus probable que vous fassiez accidentellement l'hypothèse erronée qu'un nombre rentre toujours dans un type de données. Il est probablement plus sûr de toujours utiliser int sauf si vous êtes sûr à 100% que vos chiffres seront toujours très petits. Même dans ce cas, il est peu probable que vous économisiez une quantité notable d'espace.

42
Oleksi

L'OP n'a rien dit sur le type de système pour lequel ils écrivent des programmes, mais je suppose que l'OP pensait à un PC typique avec des Go de mémoire puisque C++ est mentionné. Comme le dit l'un des commentaires, même avec ce type de mémoire, si vous avez plusieurs millions d'éléments d'un même type - comme un tableau - alors la taille de la variable peut faire une différence.

Si vous entrez dans le monde des systèmes embarqués - ce qui n'est pas vraiment hors de portée de la question, puisque l'OP ne le limite pas aux PC - alors la taille des types de données est très importante. Je viens de terminer un projet rapide sur un microcontrôleur 8 bits qui n'a que 8K mots de mémoire de programme et 368 octets de RAM. Là, évidemment, chaque octet compte. On n'utilise jamais une variable plus grande que nécessaire (à la fois du point de vue de l'espace et de la taille du code - les processeurs 8 bits utilisent beaucoup d'instructions pour manipuler les données 16 et 32 ​​bits). Pourquoi utiliser un CPU avec des ressources aussi limitées? En grandes quantités, elles peuvent coûter aussi peu qu'un quart.

Je fais actuellement un autre projet embarqué avec un microcontrôleur basé sur MIPS 32 bits qui a 512K octets de flash et 128K octets de RAM (et coûte environ 6 $ en quantité). Comme avec un PC , la taille de données "naturelle" est de 32 bits. Maintenant, il devient plus efficace, au niveau du code, d'utiliser des entiers pour la plupart des variables au lieu de caractères ou de courts métrages. contrairement aux compilateurs pour les grands systèmes, il est plus probable que les variables d'une structure seront compressées sur un système embarqué. Je prends soin de toujours essayer pour mettre toutes les variables 32 bits en premier, puis 16 bits, puis 8 bits pour éviter tout "trou".

29
tcrosley

La réponse dépend de votre système. En règle générale, voici les avantages et les inconvénients de l'utilisation de types plus petits:

Les avantages

  • Les types plus petits utilisent moins de mémoire sur la plupart des systèmes.
  • Les types plus petits permettent des calculs plus rapides sur certains systèmes. Particulièrement vrai pour float vs double sur de nombreux systèmes. Et les types int plus petits donnent également un code beaucoup plus rapide sur les processeurs 8 ou 16 bits.

Désavantages

  • De nombreux processeurs ont des exigences d'alignement. Certains accèdent aux données alignées plus rapidement que non alignées. Certains doivent aligner les données pour pouvoir même y accéder. Les types entiers plus grands équivalent à une unité alignée, ils ne sont donc probablement pas désalignés. Cela signifie que le compilateur peut être forcé de mettre vos petits entiers en plus grands. Et si les types plus petits font partie d'une structure plus grande, vous pouvez obtenir divers octets de remplissage insérés silencieusement n'importe où dans la structure par le compilateur, pour corriger l'alignement.
  • Conversions implicites dangereuses. C et C++ ont plusieurs règles obscures et dangereuses sur la façon dont les variables sont promues en plus grandes, implicitement sans transtypage. Il existe deux ensembles de règles de conversion implicites entrelacées, appelées "règles de promotion des nombres entiers" et "conversions arithmétiques habituelles". En savoir plus à leur sujet ici . Ces règles sont l'une des causes les plus courantes de bogues en C et C++. Vous pouvez éviter beaucoup de problèmes en utilisant simplement le même type entier dans tout le programme.

Mon conseil est d'aimer ça:

system                             int types

small/low level embedded system    stdint.h with smaller types
32-bit embedded system             stdint.h, stick to int32_t and uint32_t.
32-bit desktop system              Only use (unsigned) int and long long.
64-bit system                      Only use (unsigned) int and long long.

Vous pouvez également utiliser le int_leastn_t ou int_fastn_t de stdint.h, où n est le nombre 8, 16, 32 ou 64. int_leastn_t type signifie "Je veux que ce soit au moins n octets mais je me fiche que le compilateur l'alloue comme un type plus grand pour l'adapter à l'alignement".

int_fastn_t signifie "Je veux que ce soit long de n octets, mais s'il accélère l'exécution de mon code, le compilateur doit utiliser un type plus grand que celui spécifié".

Généralement, les différents types stdint.h sont une bien meilleure pratique que plain int etc, car ils sont portables. L'intention avec int était de ne pas lui donner une largeur spécifiée uniquement pour le rendre portable. Mais en réalité, il est difficile de porter car vous ne savez jamais quelle sera sa taille sur un système spécifique.

13
user29079

Selon le fonctionnement du système d'exploitation spécifique, vous vous attendez généralement à ce que la mémoire soit allouée non optimisée de sorte que lorsque vous appelez un octet, ou un mot ou un autre petit type de données à allouer, la valeur occupe un registre entier tout cela est très posséder. Le fonctionnement de votre compilateur ou interprète pour interpréter ceci est cependant autre chose, donc si vous compilez un programme en C # par exemple, la valeur pourrait occuper physiquement un registre pour elle-même, mais la valeur sera vérifiée pour vous assurer que vous ne le faites pas essayez de stocker une valeur qui dépassera les limites du type de données voulu.

En termes de performances, et si vous êtes vraiment pédant à propos de telles choses, il est probablement plus rapide d'utiliser simplement le type de données qui correspond le mieux à la taille du registre cible, mais vous passez à côté de tout ce joli sucre syntaxique qui rend le travail avec les variables si facile .

Comment cela vous aide-t-il? Eh bien, c'est vraiment à vous de décider pour quel type de situation vous codez. Pour presque tous les programmes que j'ai jamais écrits, il suffit de faire simplement confiance à votre compilateur pour optimiser les choses et utiliser le type de données qui vous est le plus utile. Si vous avez besoin d'une grande précision, utilisez les types de données à virgule flottante plus grands. Si vous ne travaillez qu'avec des valeurs positives, vous pouvez probablement utiliser un entier non signé, mais pour la plupart, il suffit d'utiliser le type de données int.

Si toutefois vous avez des exigences très strictes en matière de données, telles que l'écriture d'un protocole de communication ou une sorte d'algorithme de chiffrement, l'utilisation de types de données à plage vérifiée peut s'avérer très utile, en particulier si vous essayez d'éviter les problèmes liés aux dépassements/sous-dépassements de données ou des valeurs de données non valides.

La seule autre raison à laquelle je peux penser du haut de ma tête pour utiliser des types de données spécifiques est lorsque vous essayez de communiquer l'intention dans votre code. Si vous utilisez un raccourci par exemple, vous dites à d'autres développeurs que vous autorisez les nombres positifs et négatifs dans une très petite plage de valeurs.

11
S.Robins

Comme scarfridge a commenté, ceci est un

Cas classique de optimisation prématurée .

Essayer d'optimiser l'utilisation de la mémoire pourrait influer sur d'autres domaines de performances, et les règles d'or de l'optimisation sont:

La première règle d'optimisation de programme: Ne le faites pas .

La deuxième règle de l'optimisation des programmes (pour les experts uniquement!): Ne le faites pas encore . "

- Michael A. Jackson

Afin de savoir si le moment est venu d'optimiser, il faut des analyses comparatives et des tests. Vous devez savoir où votre code est inefficace, afin de pouvoir cibler vos optimisations.

Afin de déterminer si la version optimisée du code est réellement meilleure que l'implémentation naïve à un moment donné, vous devez les comparer côte à côte- côté avec les mêmes données.

N'oubliez pas que le fait qu'une implémentation donnée soit plus efficace sur la génération actuelle de CPU ne signifie pas qu'elle le sera toujours. Ma réponse à la question La micro-optimisation est-elle importante lors du codage? détaille un exemple d'expérience personnelle où une optimisation obsolète a entraîné un ralentissement de l'ordre de grandeur.

Sur de nombreux processeurs, les accès à la mémoire non alignés sont significativement plus coûteux que les accès à la mémoire alignés. Emballer quelques courts métrages dans votre structure peut simplement signifier que votre programme doit effectuer une opération de pack/unpack à chaque fois vous touchez l'une ou l'autre valeur.

Pour cette raison, les compilateurs modernes ignorent vos suggestions. Comme nikie commentaires:

Avec les paramètres standard du compilateur d'emballage/alignement, les variables seront de toute façon alignées sur des limites de 4 octets, donc il pourrait ne pas y avoir de différence du tout.

Devinez votre compilateur à vos risques et périls.

Il y a une place pour de telles optimisations, lorsque vous travaillez avec des ensembles de données de téraoctets ou des microcontrôleurs intégrés, mais pour la plupart d'entre nous, ce n'est pas vraiment un problème.

6
Mark Booth

Ce sera d'une sorte de OOP et/ou point de vue entreprise/application et pourrait ne pas être applicable dans certains domaines/domaines, mais je voudrais en quelque sorte évoquer le concept de obsession primitive.

Il IS une bonne idée d'utiliser différents types de données pour différents types d'informations dans votre application. Cependant, ce n'est probablement PAS une bonne idée d'utiliser les types intégrés pour cela, sauf si vous avez des problèmes sérieux problèmes de performance (qui ont été mesurés et vérifiés, etc.).

Si nous voulons modéliser les températures en Kelvin dans notre application, nous POUVONS utiliser un ushort ou uint ou quelque chose de similaire pour indiquer que "la notion de degrés négatifs Kelvin est absurde et une erreur de logique de domaine" . L'idée derrière cela est saine, mais vous n'allez pas jusqu'au bout. Ce que nous avons réalisé, c'est que nous ne pouvons pas avoir de valeurs négatives, donc c'est pratique si nous pouvons obtenir le compilateur pour nous assurer que personne n'attribue une valeur négative à une température Kelvin. Il est également vrai que vous ne pouvez pas effectuer d'opérations au niveau du bit sur les températures. Et vous ne pouvez pas ajouter une mesure de poids (kg) à une température (K). Mais si vous modélisez à la fois la température et la masse comme uints, nous pouvons le faire.

L'utilisation de types intégrés pour modéliser nos entités DOMAIN est susceptible de conduire à un code désordonné, à des vérifications manquées et à des invariants cassés. Même si un type capture QUELQUE partie de l'entité (ne peut pas être négatif), il en manquera forcément d'autres (ne peut pas être utilisé dans des expressions arithmétiques arbitraires, ne peut pas être traité comme un tableau de bits, etc.)

La solution est de définir de nouveaux types qui encapsule les invariants. De cette façon, vous pouvez vous assurer que l'argent est de l'argent et les distances sont des distances, et vous ne pouvez pas les additionner, et vous ne pouvez pas créer une distance négative, mais vous POUVEZ créer un montant d'argent (ou une dette) négatif. Bien sûr, ces types utiliseront les types intégrés en interne, mais c'est caché des clients. En ce qui concerne votre question sur les performances/la consommation de mémoire, ce genre de chose peut vous permettre de changer la façon dont les choses sont stockées en interne sans changer l'interface de vos fonctions qui opèrent sur vos entités de domaine, si vous découvrez que putain, un short est tout simplement trop grand.

3
sara

La principale différence étant que short int prend 2 octets de mémoire tandis que int prend 4 octets, et short int a une valeur moindre, mais nous pourrions également appeler cela pour le rendre encore plus petit:

Ceci est une erreur. Vous ne pouvez pas faire d'hypothèses sur le nombre d'octets que chaque type contient, autre que char étant un octet et au moins 8 bits par octet, la taille de chaque type étant supérieure ou égale à la précédente.

Les avantages en termes de performances sont incroyablement minuscules pour les variables de pile - ils seront probablement alignés/remplis de toute façon.

Pour cette raison, short et long n'ont pratiquement aucune utilité de nos jours, et il est presque toujours préférable d'utiliser int.


Bien sûr, il y a aussi stdint.h qui est parfaitement bien à utiliser lorsque int ne le coupe pas. Si jamais vous allouez d'énormes tableaux d'entiers/structures, alors un intX_t est logique car vous pouvez être efficace et vous fier à la taille du type. Ce n'est pas du tout prématuré car vous pouvez économiser des mégaoctets de mémoire.

3
Pubby

Oui bien sûr. C'est une bonne idée d'utiliser uint_least8_t pour les dictionnaires, les énormes tableaux de constantes, les tampons, etc. Il est préférable d'utiliser uint_fast8_t à des fins de traitement.

uint8_least_t (stockage) -> uint8_fast_t (traitement) -> uint8_least_t (espace de rangement).

Par exemple, vous prenez le symbole 8 bits de source, les codes 16 bits de dictionaries et quelques 32 bits constants. Ensuite, vous traitez avec elles des opérations de 10 à 15 bits et sortez 8 bits destination.

Imaginons que vous deviez traiter 2 gigaoctets de source. Le nombre d'opérations sur les bits est énorme. Vous recevrez un excellent bonus de performance si vous passez à des types rapides pendant le traitement. Les types rapides peuvent être différents pour chaque famille de CPU. Vous pouvez inclure stdint.h et utilise uint_fast8_t, uint_fast16_t, uint_fast32_t, etc.

Vous pouvez utiliser uint_least8_t au lieu de uint8_t pour la portabilité. Mais personne ne sait réellement quel processeur moderne utilisera cette fonctionnalité. VAC machine est une pièce de musée. Alors peut-être que c'est une surpuissance.

1
puchu