web-dev-qa-db-fra.com

performances des entiers non signés vs signés

Y a-t-il un gain/une perte de performances en utilisant des entiers non signés sur des entiers signés?

Si oui, cela vaut-il aussi pour le court et le long?

64
Flexo

La division par puissances de 2 est plus rapide avec unsigned int, car il peut être optimisé en une seule instruction de décalage. Avec signed int, cela nécessite généralement plus d'instructions machine, car la division arrondit vers zéro, mais se déplace vers la droite vers le bas. Exemple:

int foo(int x, unsigned y)
{
    x /= 8;
    y /= 8;
    return x + y;
}

Voici la partie x pertinente (division signée):

movl 8(%ebp), %eax
leal 7(%eax), %edx
testl %eax, %eax
cmovs %edx, %eax
sarl $3, %eax

Et voici la partie y pertinente (division non signée):

movl 12(%ebp), %edx
shrl $3, %edx
98
fredoverflow

En C++ (et C), le débordement d'entier signé n'est pas défini, tandis que le débordement d'entier non signé est défini pour boucler. Notez que par exemple dans gcc, vous pouvez utiliser l'indicateur -fwrapv pour définir un débordement signé (pour boucler).

Le débordement d'entier signé non défini permet au compilateur de supposer que les débordements ne se produisent pas, ce qui peut introduire des opportunités d'optimisation. Voir par exemple ce billet de blog pour discussion.

44
kbjorklu

unsigned conduit à des performances identiques ou meilleures que signed. Quelques exemples:

  • Division par une constante qui est une puissance de 2 (voir aussi la réponse de FredOverflow)
  • Division par un nombre constant (par exemple, mon compilateur implémente la division par 13 en utilisant 2 instructions asm pour non signé et 6 instructions pour signé)
  • Vérifier si un nombre est pair (je ne sais pas pourquoi mon compilateur MS Visual Studio l'implémente avec 4 instructions pour les nombres signed; gcc le fait avec 1 instruction, tout comme dans le cas unsigned)

short conduit généralement à des performances identiques ou pires que int (en supposant sizeof(short) < sizeof(int)). La dégradation des performances se produit lorsque vous affectez le résultat d'une opération arithmétique (qui est généralement int, jamais short) à une variable de type short, qui est stockée dans le registre du processeur (qui est également de type int). Toutes les conversions de short à int prennent du temps et sont ennuyeuses.

Remarque: certains DSP ont des instructions de multiplication rapide pour le signed short type; dans ce cas précis, short est plus rapide que int.

Quant à la différence entre int et long, je ne peux que deviner (je ne connais pas les architectures 64 bits). Bien sûr, si int et long ont la même taille (sur les plates-formes 32 bits), leurs performances sont également les mêmes.


Un ajout très important, souligné par plusieurs personnes:

Ce qui compte vraiment pour la plupart des applications, c'est l'empreinte mémoire et la bande passante utilisée. Vous devez utiliser les plus petits entiers nécessaires (short, peut-être même signed/unsigned char) pour les grands tableaux.

Cela donnera de meilleures performances, mais le gain est non linéaire (c'est-à-dire pas d'un facteur 2 ou 4) et quelque peu imprévisible - cela dépend de la taille du cache et de la relation entre les calculs et les transferts de mémoire dans votre application.

18
anatolyg

Cela dépendra de la mise en œuvre exacte. Dans la plupart des cas, il n'y aura cependant aucune différence. Si vous vous souciez vraiment, vous devez essayer toutes les variantes que vous considérez et mesurer les performances.

16
sharptooth

Cela dépend à peu près du processeur spécifique.

Sur la plupart des processeurs, il existe des instructions pour l'arithmétique signée et non signée, donc la différence entre l'utilisation d'entiers signés et non signés dépend de celle que le compilateur utilise.

Si l'un des deux est plus rapide, il est entièrement spécifique au processeur, et la différence est probablement minuscule, si elle existe.

La différence de performances entre les entiers signés et non signés est en fait plus générale que ne le suggère la réponse d'acceptation. La division d'un entier non signé par n'importe quelle constante peut être rendue plus rapide que la division d'un entier signé par une constante, que la constante soit ou non une puissance de deux. Voir http://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html

À la fin de son poste, il comprend la section suivante:

Une question naturelle est de savoir si la même optimisation pourrait améliorer la division signée; malheureusement, il semble que non, pour deux raisons:

L'incrément du dividende doit devenir une augmentation de la magnitude, c'est-à-dire incrémenter si n> 0, décrémenter si n <0. Ceci introduit une dépense supplémentaire.

La pénalité pour un diviseur non coopératif n'est que de moitié environ dans la division signée, ce qui laisse une fenêtre plus petite pour les améliorations.

Ainsi, il apparaît que l'algorithme d'arrondi pourrait être fait fonctionner en division signée, mais serait moins performant que l'algorithme d'arrondi standard.

6
David Stone

Non seulement la division par puissances de 2 est plus rapide avec le type non signé, la division par toutes les autres valeurs est également plus rapide avec le type non signé. Si vous regardez tableaux d'instructions d'Agner Fog vous verrez que les divisions non signées ont des performances similaires ou meilleures que les versions signées

Par exemple avec l'AMD K7

╔═════════════╤══════════╤═════╤═════════╤═══════════════════════╗
║ Instruction │ Operands │ Ops │ Latency │ Reciprocal throughput ║
╠═════════════╪══════════╪═════╪═════════╪═══════════════════════╣
║ DIV         │ r8/m8    │ 32  │ 24      │ 23                    ║
║ DIV         │ r16/m16  │ 47  │ 24      │ 23                    ║
║ DIV         │ r32/m32  │ 79  │ 40      │ 40                    ║
║ IDIV        │ r8       │ 41  │ 17      │ 17                    ║
║ IDIV        │ r16      │ 56  │ 25      │ 25                    ║
║ IDIV        │ r32      │ 88  │ 41      │ 41                    ║
║ IDIV        │ m8       │ 42  │ 17      │ 17                    ║
║ IDIV        │ m16      │ 57  │ 25      │ 25                    ║
║ IDIV        │ m32      │ 89  │ 41      │ 41                    ║
╚═════════════╧══════════╧═════╧═════════╧═══════════════════════╝

La même chose s'applique à Intel Pentium

╔═════════════╤══════════╤══════════════╗
║ Instruction │ Operands │ Clock cycles ║
╠═════════════╪══════════╪══════════════╣
║ DIV         │ r8/m8    │ 17           ║
║ DIV         │ r16/m16  │ 25           ║
║ DIV         │ r32/m32  │ 41           ║
║ IDIV        │ r8/m8    │ 22           ║
║ IDIV        │ r16/m16  │ 30           ║
║ IDIV        │ r32/m32  │ 46           ║
╚═════════════╧══════════╧══════════════╝

Bien sûr, ceux-ci sont assez anciens. Les architectures plus récentes avec plus de transistors pourraient combler l'écart, mais les choses de base s'appliquent: généralement, vous avez besoin de plus de macro ops, plus de logique, plus de latence pour effectuer une division signée

4
phuclv

En bref, ne vous embêtez pas avant le fait. Mais ne vous embêtez pas après.

Si vous voulez avoir des performances, vous devez utiliser des optimisations de performances d'un compilateur qui peuvent aller à l'encontre du bon sens. Une chose à retenir est que différents compilateurs peuvent compiler du code différemment et qu'ils ont eux-mêmes différents types d'optimisations. Si nous parlons d'un g++ compilateur et parler de maximiser son niveau d'optimisation en utilisant -Ofast, ou au moins un -O3 flag, d'après mon expérience, il peut compiler le type long en code avec des performances encore meilleures que n'importe quel type unsigned, ou même simplement int.

Cela vient de ma propre expérience et je vous recommande d'écrire d'abord votre programme complet et de ne vous soucier de ces choses qu'après cela, lorsque vous avez votre code réel entre les mains et que vous pouvez le compiler avec des optimisations pour essayer de choisir les types qui fonctionnent réellement. meilleur. C'est également une bonne suggestion très générale sur l'optimisation du code pour les performances, écrivez d'abord rapidement, essayez de compiler avec des optimisations, ajustez les choses pour voir ce qui fonctionne le mieux. Et vous devriez également essayer d'utiliser différents compilateurs pour compiler votre programme et choisir celui qui génère le code machine le plus performant.

Un programme de calcul d'algèbre linéaire multi-thread optimisé peut facilement avoir une différence de performance> 10x finement optimisé vs non optimisé. Donc ça compte.

La sortie de l'optimiseur contredit la logique dans de nombreux cas. Par exemple, j'ai eu un cas où une différence entre a[x]+=b et a[x]=b a changé le temps d'exécution du programme presque 2x. Et non, a[x]=b n'était pas le plus rapide.

Voici par exemple indiquant NVidia que pour programmer leurs GPU:

Remarque: Comme c'était déjà la meilleure pratique recommandée, l'arithmétique signée devrait être préférée à l'arithmétique non signée dans la mesure du possible pour un meilleur débit sur SMM. La norme du langage C place plus de restrictions sur le comportement de dépassement pour les mathématiques non signées, limitant les opportunités d'optimisation du compilateur.

3
Íhor Mé

IIRC, sur x86 signé/non signé ne devrait faire aucune différence. Short/long, d'autre part, est une histoire différente, car la quantité de données qui doit être déplacée vers/depuis RAM est plus grande pour les longs (d'autres raisons peuvent inclure des opérations de cast comme l'extension un court à long).

1
CAFxX

Les entiers signés et non signés fonctionneront toujours comme des instructions d'horloge unique et auront les mêmes performances en lecture-écriture, mais selon Dr Andrei Alexandresc non signé est préférable à signé. La raison en est que vous pouvez tenir deux fois plus de nombres dans le même nombre de bits, car vous ne perdez pas le bit de signe et vous utiliserez moins d'instructions pour vérifier les nombres négatifs, ce qui augmente les performances de la ROM réduite. D'après mon expérience avec la Kabuki VM , qui dispose d'une implémentation ultra-haute performance Script , il est rare que vous ayez réellement besoin d'un numéro signé lorsque vous travaillez avec de la mémoire. J'ai passé des années à faire de l'arithmétique de pointeur avec des nombres signés et non signés et je n'ai trouvé aucun avantage pour le signé quand aucun bit de signe n'est nécessaire.

Lorsque signé peut être préféré, c'est lorsque vous utilisez le décalage de bits pour effectuer la multiplication et la division des puissances de 2, car vous pouvez effectuer des puissances négatives de 2 divisions avec les entiers complémentaires de 2 signés. Veuillez voir plus de vidéos YouTube d'Andrei pour plus de techniques d'optimisation. Vous pouvez également trouver de bonnes informations dans mon article sur le l'algorithme de conversion Integer-to-String le plus rapide au monde .

1
user2356685

Traditionnellement, int est le format entier natif de la plate-forme matérielle cible. Tout autre type entier peut entraîner des pénalités de performances.

ÉDITER:

Les choses sont légèrement différentes sur les systèmes modernes:

  • int peut en fait être 32 bits sur les systèmes 64 bits pour des raisons de compatibilité. Je crois que cela se produit sur les systèmes Windows.

  • Les compilateurs modernes peuvent implicitement utiliser int lors de l'exécution de calculs pour des types plus courts dans certains cas.

0
thkala

Un entier non signé est avantageux en ce que vous stockez et traitez les deux comme un flux binaire, je veux dire juste une donnée, sans signe, donc la multiplication, la déviation devient plus facile (plus rapide) avec les opérations de décalage de bit

0