46 votes

Pourquoi un compilateur n'optimise-t-il pas les opérations en virgule flottante *2 en une augmentation d'exposant?

J'ai souvent remarqué que gcc convertit souvent les multiplications en décalages dans l'exécutable. Quelque chose de similaire peut se produire lors de la multiplication d'un int et d'un float. Par exemple, 2 * f, pourrait simplement augmenter l'exposant de f de 1, économisant ainsi quelques cycles. Les compilateurs, peut-être si on les y invite (par exemple via -ffast-math), le font-ils en général ?

Les compilateurs sont-ils généralement assez intelligents pour le faire, ou dois-je le faire moi-même en utilisant la famille de fonctions scalb*() ou ldexp()/frexp() ?

10 votes

Quelle est votre question ?

3 votes

La question est pourquoi le compilateur ne convertit-il pas les multiplications par 2 à virgule flottante en incréments d'exposant comme il le peut pour les entiers et les décalages.

1 votes

Je sais que cela peut se faire, mais est-ce que les compilateurs en général le font ? Sont-ils déjà assez intelligents ? Ou dois-je le faire moi-même ?

85voto

Mysticial Points 180300

Par exemple, 2 * f, pourrait simplement incrémenter l'exposant de f de 1, économisant ainsi quelques cycles.

Ce n'est tout simplement pas vrai.

D'abord, il y a trop de cas particuliers tels que zéro, l'infini, NaN et les dénormalisés. Ensuite, il y a le problème de performance.

La confusion réside dans le fait qu'incrémenter l'exposant n'est pas plus rapide que faire une multiplication.

Si vous regardez les instructions matérielles, il n'y a aucun moyen direct d'incrémenter l'exposant. Donc, ce que vous devez faire à la place est :

  1. Convertir en entier par des opérations sur les bits.
  2. Incrémenter l'exposant.
  3. Reconvertir en flottant par des opérations sur les bits.

Il y a généralement une latence moyenne à grande pour déplacer des données entre les unités d'exécution entières et flottantes. Ainsi, cette "optimisation" devient finalement beaucoup plus lente qu'une simple multiplication en virgule flottante.

Donc, la raison pour laquelle le compilateur ne fait pas cette "optimisation" est parce que ce n'est pas plus rapide.

0 votes

Instructions matérielles de quel CPU ? Il peut exister un processeur qui possède une telle instruction.

4 votes

Je suppose que c'est x86 car tu n'as pas spécifié. Mais ce n'est pas très différent pour d'autres architectures.

0 votes

D'accord, mais convertir en entier bit à bit? Les exposants sont des entiers, aucune conversion n'est nécessaire.

24voto

R.. Points 93718

Sur les unités centrales de traitement (CPU) modernes, la multiplication a généralement un débit d'un cycle par opération et une latence faible. Si la valeur est déjà dans un registre en virgule flottante, il n'y a aucun moyen de surpasser cela en la manipulant pour effectuer des opérations arithmétiques entières sur la représentation. Si elle est en mémoire dès le départ, et si vous supposez que ni la valeur actuelle ni le résultat correct ne serait zéro, dénormalisé, nan, ou infini, alors il pourrait être plus rapide d'effectuer quelque chose comme

addl $0x100000, 4(%eax)   # exemple d'ASM x86

pour multiplier par deux; la seule fois où je pourrais voir cela être bénéfique est si vous traitez un tableau entier de données en virgule flottante qui est borné loin de zéro et de l'infini, et si l'échelle par une puissance de deux est la seule opération que vous effectuerez (donc vous n'avez aucune raison existante de charger les données dans des registres en virgule flottante).

0 votes

+1, auquel cas, vous pourriez simplement additionner tous ces facteurs d'échelle et le faire une seule fois à la fin.

2 votes

+1 pour avoir donné un cas possible où cela pourrait effectivement être plus rapide (même si je doute que cela vaille jamais la peine de le faire)

2 votes

Si vous travaillez avec un audio en virgule flottante à haut taux d'échantillonnage sur un système basse de gamme/embraqué à plusieurs canaux et passez principalement à travers, ce type d'optimisation peut être utile.

22voto

Eric Postpischil Points 36641

Les formats de nombres flottants courants, en particulier IEEE 754, ne stockent pas l'exposant en tant qu'entier simple, et le traiter comme un entier ne produira pas de résultats corrects.

En flottant 32 bits ou double 64 bits, le champ de l'exposant est de 8 ou 11 bits, respectivement. Les exposants codés de 1 à 254 (en flottant) ou de 1 à 2046 (en double) agissent comme des entiers : si vous ajoutez un à l'une de ces valeurs et que le résultat est l'une de ces valeurs, alors la valeur représentée double. Cependant, ajouter un échoue dans ces situations :

  • La valeur initiale est 0 ou subnormale. Dans ce cas, le champ de l'exposant commence à zéro, et en ajoutant un, on ajoute 2-126 (en flottant) ou 2-1022 (en double) au nombre ; cela ne double pas le nombre.
  • La valeur initiale dépasse 2127 (en flottant) ou 21023 (en double). Dans ce cas, le champ de l'exposant commence à 254 ou 2046, et en ajoutant un, on change le nombre en un NaN ; cela ne double pas le nombre.
  • La valeur initiale est l'infini ou un NaN. Dans ce cas, le champ de l'exposant commence à 255 ou 2047, et en ajoutant un, on le change en zéro (et il est susceptible de déborder dans le bit de signe). Le résultat est zéro ou subnormal mais devrait être l'infini ou un NaN, respectivement.

(Ce qui précède est pour les signes positifs. La situation est symétrique avec les signes négatifs.)

Comme d'autres l'ont noté, certains processeurs n'ont pas de moyens pour manipuler rapidement les bits de valeurs à virgule flottante. Même sur ceux qui le font, le champ de l'exposant n'est pas isolé des autres bits, donc en général, vous ne pouvez pas ajouter un sans déborder dans le bit de signe dans le dernier cas mentionné ci-dessus.

Bien que certaines applications puissent tolérer des raccourcis tels que négliger les subnormales ou les NaN ou même les infinis, il est rare que les applications puissent ignorer zéro. Étant donné que l'ajout d'un à l'exposant échoue à manipuler correctement zéro, il n'est pas utilisable.

0 votes

Comme vous le remarquez, vous pourriez savoir ou supposer que le flottant n'est pas nul à l'avance, même si c'est rare.

10voto

Aki Suihkonen Points 9888

Ce n'est pas une question d'érudition des compilateurs ou des rédacteurs de compilateurs. C'est plus une question de respect des normes et de production de tous les « effets secondaires » nécessaires tels que les Infs, les Nans et les dénormaux.

Cela peut également concerner le fait de ne pas produire d'autres effets secondaires non nécessaires, comme la lecture de la mémoire. Mais je reconnais que cela peut être plus rapide dans certaines circonstances.

0 votes

Peut-être dans votre domaine, je me fiche des NaNs. N'oubliez pas -ffast-math non plus.

5 votes

@user1095108 Mais comment le compilateur est-il censé savoir que cela ne vous intéresse pas?

0 votes

@user1095108 - wow, que voulez-vous dire, que vous vous fichez des NaN? Vous tenez compte de la norme ISO?

3voto

Simon Richter Points 11471

En fait, c'est ce qui se passe dans le matériel.

Le 2 est également transmis au FPU sous forme de nombre flottant, avec une mantisse de 1,0 et un exposant de 2^1. Pour la multiplication, les exposants sont ajoutés et les mantisses sont multipliées.

Étant donné qu'il existe du matériel dédié pour gérer le cas complexe (multiplication par des valeurs qui ne sont pas des puissances de deux), et que le cas spécial n'est pas traité plus mal qu'avec du matériel dédié, il est inutile d'avoir des circuits et des instructions supplémentaires.

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