Question

Sur un processeur 32 bits, un entier est 4 octets et un entier court 2 octets. Si j’écris une application C / C ++ qui utilise de nombreuses valeurs numériques qui tiendront toujours dans la plage d’un entier court, est-il plus efficace d’utiliser des entiers sur 4 octets ou des entiers sur 2 octets?

J'ai entendu dire que les entiers sur 4 octets sont plus efficaces, car cela correspond à la bande passante du bus de la mémoire à la CPU. Toutefois, si j’ajoute deux entiers courts, la CPU conditionnera-t-elle les deux valeurs en un seul passage en parallèle (couvrant ainsi la bande passante du bus sur 4 octets)?

Était-ce utile?

La solution

Oui, vous devez absolument utiliser un entier 32 bits sur un processeur 32 bits, sinon il pourrait masquer les bits inutilisés (c’est-à-dire qu’il fera toujours le calcul en 32 bits, puis convertira la réponse en 16 bits)

Il ne fera pas deux opérations 16 bits à la fois pour vous, mais si vous écrivez le code vous-même et que vous êtes sûr qu'il ne débordera pas, vous pourrez le faire vous-même.

Modifier : je dois ajouter que cela dépend aussi un peu de votre définition de "efficace". Bien qu'il puisse effectuer des opérations 32 bits plus rapidement, vous utiliserez bien entendu deux fois plus de mémoire.

Si ceux-ci sont utilisés pour des calculs intermédiaires dans une boucle interne quelque part, utilisez 32 bits. Si, toutefois, vous lisez ceci à partir du disque, ou même si vous devez simplement payer pour un cache manquant, il peut être préférable d'utiliser des entiers 16 bits. Comme pour toutes les optimisations, il n'y a qu'une seule façon de savoir: le profiler .

Autres conseils

Si vous avez un grand nombre de nombres, choisissez la plus petite taille possible. Il sera plus efficace de travailler avec un tableau de courts-métrages de 16 bits que d’ints de 32 bits, car la densité de la mémoire cache est deux fois plus grande. Le coût de toute extension de signe que la CPU doit faire pour travailler avec des valeurs 16 bits dans des registres 32 bits est négligeable par rapport au coût d'un échec de cache.

Si vous utilisez simplement des variables de membre dans des classes mélangées à d'autres types de données, il est moins clair, car les exigences en matière de remplissage suppriment tout avantage en termes d'économie d'espace des valeurs 16 bits.

Si vous utilisez " plusieurs " valeurs entières, le goulot d’étranglement de votre traitement risque d’être de la bande passante en mémoire. Les entiers 16 bits entrent plus étroitement dans le cache de données, ce qui représente un gain de performances.

Si vous travaillez avec une très grande quantité de données, vous devriez lire Ce que tout programmeur devrait faire Connaître la mémoire par Ulrich Drepper. Concentrez-vous sur le chapitre 6, sur l’optimisation de l’efficacité du cache de données.

Une CPU 32 bits est une CPU qui opère généralement en interne sur des valeurs 32 bits, mais cela ne signifie pas pour autant qu’elle soit plus lente lorsque vous effectuez la même opération sur une valeur de 8/16 bits. x86 par exemple, toujours compatible avec les versions antérieures jusqu'au 8086, peut fonctionner sur des fractions de registre. Cela signifie que même si un registre a une largeur de 32 bits, il ne peut fonctionner que sur les 16 premiers ou les 8 premiers bits de ce registre et aucun ralentissement ne se produira. Ce concept a même été adopté par x86_64, où les registres sont à 64 bits, mais ils ne peuvent toujours fonctionner que sur les 32, 16 ou 8 premiers bits.

De plus, les processeurs x86 chargent toujours une ligne de cache complète à partir de la mémoire, même s’ils ne sont pas déjà dans le cache, et qu’une ligne de cache dépasse 4 octets (pour les processeurs 32 bits plutôt que 8 ou 16 octets), le chargement de 2 octets à partir de la mémoire est donc important. aussi rapidement que le chargement de 4 octets de la mémoire. Si vous traitez beaucoup de valeurs en mémoire, les valeurs 16 bits peuvent en réalité être beaucoup plus rapides que les valeurs 32 bits, car les transferts de mémoire sont moins nombreux. Si une ligne de cache a 8 octets, il y a quatre valeurs de 16 bits par ligne de cache, mais seulement deux valeurs de 32 bits. Ainsi, lorsque vous utilisez une entrée de 16 bits, vous disposez d'un accès en mémoire toutes les quatre valeurs. En utilisant une entrée de 32 bits, vous en avez une toutes les deux. , ce qui entraîne deux fois plus de transferts pour le traitement d’un grand tableau int.

D'autres processeurs, comme PPC par exemple, ne peuvent traiter qu'une fraction d'un registre, ils traitent toujours le registre complet. Pourtant, ces processeurs ont généralement des opérations de chargement spéciales qui leur permettent, par exemple, charger une valeur de 16 bits à partir de la mémoire, l'étendre à 32 bits et l'écrire dans un registre. Plus tard, ils ont une opération de stockage spéciale qui prend la valeur du registre et ne stocke que les 16 derniers bits en mémoire; les deux opérations ne nécessitent qu'un seul cycle de processeur, tout comme une charge / mémoire 32 bits aurait besoin, de sorte qu'il n'y a pas de différence de vitesse. Et comme PPC ne peut effectuer que des opérations arithmétiques sur les registres (contrairement à x86, qui peut également fonctionner directement sur la mémoire), cette procédure de chargement / stockage a lieu de toute façon, que vous utilisiez des bits 32 bits ou 16 bits.

Le seul inconvénient, si vous enchaînez plusieurs opérations sur un processeur 32 bits ne pouvant fonctionner que sur des registres complets, est que le résultat 32 bits de la dernière opération peut devoir être "coupé". 16 bits avant la prochaine opération, sinon le résultat risque d’être incorrect. Une telle réduction ne représente cependant qu'un seul cycle de processeur (opération simple AND), et les compilateurs savent très bien déterminer à quel moment une telle réduction est vraiment nécessaire et si le fait de l'omettre n'a aucune influence sur le résultat final. Donc, une telle réduction n’est pas effectuée après chaque instruction, elle est seulement effectuée si elle est vraiment inévitable. Certains processeurs proposent diverses options "améliorées". des instructions qui rendent une telle réduction inutile et j'ai vu beaucoup de code dans ma vie, où je m'attendais à une telle réduction, mais en regardant le code assemblé généré, le compilateur a trouvé un moyen de l'éviter complètement.

Donc, si vous attendez une règle générale ici, je devrai vous décevoir. On ne peut pas non plus affirmer avec certitude que les opérations 16 bits sont aussi rapides que les opérations 32 bits, personne ne peut affirmer avec certitude que les opérations 32 bits seront toujours plus rapides. Cela dépend aussi de ce que votre code fait exactement avec ces chiffres et comment il le fait. J'ai vu des points de repère où les opérations 32 bits étaient plus rapides sur certains processeurs 32 bits que le même code avec des opérations 16 bits, mais j'ai aussi déjà vu le contraire. Même passer d'un compilateur à un autre ou mettre à niveau votre version du compilateur peut déjà tout changer. Je ne peux que dire ce qui suit: Quiconque prétend que travailler avec des courts métrages est beaucoup plus lent que de travailler avec ints doit fournir un exemple de code source pour cette revendication, ainsi que le nom du processeur et du compilateur qu’il a utilisé pour les tests, car je n’ai jamais rien expérimenté de tel dans ce domaine. sur les 10 dernières années. Il peut y avoir des cas où travailler avec ints est peut-être 1 à 5% plus rapide, mais rien de moins de 10% n’est "significatif". et la question est de savoir si cela vaut la peine de gaspiller deux fois plus de mémoire dans certains cas uniquement parce que cela peut vous acheter 2% de performance? Je ne pense pas.

Cela dépend. Si vous êtes lié au processeur, les opérations 32 bits sur un processeur 32 bits seront plus rapides que 16 bits. Si vous êtes lié à la mémoire (en particulier si vous avez trop d’échantillons de cache L2), utilisez les données les plus petites dans lesquelles vous puissiez entrer.

Vous pouvez trouver celui que vous utilisez, un profileur qui mesurera à la fois les échecs CPU et L2, comme VTune d’Intel . Vous exécuterez votre application deux fois avec la même charge et fusionnera les deux exécutions en une vue des zones réactives de votre application. Vous pourrez ainsi voir pour chaque ligne de code le nombre de cycles passés sur cette ligne. Si une ligne de code coûteuse affiche 0 cache manquant, vous êtes lié au processeur. Si vous voyez des tonnes de ratés, vous êtes lié à la mémoire.

N'écoutez pas le conseil, essayez-le.

Cela dépendra probablement beaucoup du matériel / du compilateur que vous utilisez. Un test rapide devrait permettre de résoudre rapidement cette question. Probablement moins de temps pour écrire le test que pour écrire la question ici.

Si vous travaillez sur un jeu de données volumineux, l’empreinte mémoire est la principale préoccupation. Dans ce cas, un bon modèle consiste à supposer que le processeur est extrêmement rapide et à vous préoccuper du volume de données à déplacer de / vers la mémoire. En fait, les processeurs sont maintenant si rapides qu'il est parfois plus efficace de coder (par exemple, compresser) les données. De cette façon, la CPU travaille (potentiellement beaucoup plus) (décodage / codage), mais la bande passante mémoire est considérablement réduite.

Ainsi, si votre jeu de données est volumineux, il vaut probablement mieux utiliser des entiers 16 bits. Si votre liste est triée, vous pouvez concevoir un schéma de codage impliquant un codage différentiel ou par longueur, ce qui réduira encore plus la bande passante mémoire.

Quand vous dites 32 bits, je suppose que vous voulez dire x86. L'arithmétique 16 bits est assez lente: le préfixe de taille d'opérande rend le décodage très lent . Donc, ne faites pas vos variables temporaires short int ou int16_t.

Cependant, x86 peut charger efficacement des entiers 16 et 8 bits dans des registres 32 ou 64 bits. (movzx / movsx: zéro et extension du signe). N'hésitez donc pas à utiliser short int pour les tableaux et les champs de structure, mais assurez-vous que vous utilisez int ou long pour vos variables temporaires.

  

Toutefois, si j’ajoute deux entiers courts, la CPU conditionnera-t-elle les deux valeurs en une seule passe en parallèle (couvrant ainsi la bande passante de 4 octets du bus)?

C'est un non-sens. Les instructions de chargement / stockage interagissent avec le cache N1 et le facteur limitant est le nombre d'opérations; la largeur est sans importance. par exemple. sur core2: 1 charge et 1 magasin par cycle, quelle que soit la largeur. Le cache L1 a un chemin de 128 ou 256 bits vers le cache L2.

Si les charges sont votre goulot d’étranglement, une charge large que vous séparez avec des décalages ou des masques après le chargement peut aider. Ou utilisez SIMD pour traiter les données en parallèle sans décompresser après le chargement en parallèle.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top