Dois-je utiliser la multiplication ou la division?

118

Voici une question amusante idiote:

Disons que nous devons effectuer une opération simple où nous avons besoin de la moitié de la valeur d'une variable. Il existe généralement deux façons de procéder:

y = x / 2.0;
// or...
y = x * 0.5;

En supposant que nous utilisons les opérateurs standard fournis avec le langage, lequel offre les meilleures performances?

Je suppose que la multiplication est généralement meilleure, alors j'essaie de m'en tenir à cela lorsque je code, mais j'aimerais le confirmer.

Bien que personnellement je sois intéressé par la réponse pour Python 2.4-2.5, n'hésitez pas à publier également une réponse pour d'autres langues! Et si vous le souhaitez, n'hésitez pas à publier d'autres méthodes plus sophistiquées (comme l'utilisation d'opérateurs de décalage au niveau du bit).

Edmundito
la source
5
Avez-vous réalisé un benchmark? Il ne s'agit que d'une douzaine de lignes de code. Qu'avez-vous appris en exécutant un benchmark? [Indice: cela aurait été plus rapide que de poster la question ici.]
S.Lott
4
Excellente question, qui a généré des réponses / discussions assez intéressantes. Merci :)
stealthcopter
22
Même s'il avait appris la réponse en la comparant, cela reste une question utile et a généré des réponses intéressantes et utiles. Je souhaite également que les gens s'en tiennent à l'essentiel et s'abstiennent d'écrire des réponses et des commentaires aux réponses offrant des conseils non pertinents sur la pertinence ou non de l'optimisation en question. Pourquoi ne pas supposer que l'OP pose la question telle qu'elle est écrite au lieu de supposer qu'il ou elle veut «vraiment» des conseils sur une réécriture à plus grande échelle.
Kevin Whitefoot
1
La division est beaucoup plus lente que la multiplication. Mais certains compleurs / machines virtuelles intelligents transforment la division en multiplication, de sorte que vos tests auront les mêmes résultats (les deux tests testent la multiplication).
Ivan Kuckir le
4
Un peu hors sujet, mais je veux juste dire à quel point je suis d'accord avec @KevinWhitefoot. Il n'y a rien de plus frustrant que de lire des sermonneurs plutôt que des réponses techniques à des questions techniques. Merci Kevin pour ton commentaire!
Jean-François

Réponses:

78

Python:

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 / 2.0'
real    0m26.676s
user    0m25.154s
sys     0m0.076s

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 * 0.5'
real    0m17.932s
user    0m16.481s
sys     0m0.048s

la multiplication est 33% plus rapide

Lua:

time lua -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m7.956s
user    0m7.332s
sys     0m0.032s

time lua -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m7.997s
user    0m7.516s
sys     0m0.036s

=> pas de réelle différence

LuaJIT:

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m1.921s
user    0m1.668s
sys     0m0.004s

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m1.843s
user    0m1.676s
sys     0m0.000s

=> c'est seulement 5% plus rapide

conclusions: en Python, il est plus rapide de se multiplier que de diviser, mais à mesure que vous vous rapprochez du processeur en utilisant des VM ou des JIT plus avancés, l'avantage disparaît. Il est fort possible qu'une future machine virtuelle Python la rende inutile

Javier
la source
Merci pour le conseil sur l'utilisation de la commande time pour l'analyse comparative!
Edmundito
2
Votre conclusion est fausse. Cela devient plus pertinent à mesure que le JIT / VM s'améliore. La division est plus lente par rapport à la surcharge inférieure de la VM. Rappelez-vous que les compilateurs ne peuvent généralement pas optimiser beaucoup la virgule flottante pour garantir la précision.
rasmus le
7
@rasmus: Au fur et à mesure que le JIT s'améliore, il devient plus probable d'utiliser une instruction de multiplication du processeur même si vous avez demandé une division.
Ben Voigt
68

Utilisez toujours ce qui est le plus clair. Tout ce que vous faites est d'essayer de déjouer le compilateur. Si le compilateur est un peu intelligent, il fera de son mieux pour optimiser le résultat, mais rien ne peut faire en sorte que le prochain type ne vous déteste pas pour votre solution de changement de bits merdique (j'aime peu la manipulation au fait, c'est amusant. Mais amusant! = Lisible )

L'optimisation prématurée est la racine de tout Mal. Souvenez-vous toujours des trois règles d'optimisation!

  1. N'optimisez pas.
  2. Si vous êtes un expert, consultez la règle n ° 1
  3. Si vous êtes un expert et pouvez justifier le besoin, utilisez la procédure suivante:

    • Codez-le non optimisé
    • Déterminez à quelle vitesse est «Assez rapide» - Notez quelle exigence / histoire de l'utilisateur nécessite cette métrique.
    • Rédiger un test de vitesse
    • Testez le code existant - Si c'est assez rapide, vous avez terminé.
    • Recoder optimisé
    • Testez le code optimisé. S'il ne correspond pas à la métrique, jetez-le et conservez l'original.
    • S'il répond au test, conservez le code d'origine sous forme de commentaires

De plus, faire des choses comme supprimer les boucles internes quand elles ne sont pas nécessaires ou choisir une liste chaînée sur un tableau pour un tri par insertion ne sont pas des optimisations, juste de la programmation.

Bill K
la source
7
ce n'est pas la citation complète de Knuth; voir en.wikipedia.org/wiki/…
Jason S
Non, il existe environ 40 citations différentes sur le sujet provenant d'autant de sources différentes. J'en ai en quelque sorte rassemblé quelques-uns.
Bill K
Votre dernière phrase ne permet pas de savoir quand appliquer les règles n ° 1 et n ° 2, ce qui nous ramène là où nous avons commencé: nous devons décider quelles optimisations valent la peine et lesquelles ne le sont pas. Prétendre que la réponse est évidente n'est pas une réponse.
Matt
2
C'est vraiment déroutant pour vous? Appliquez toujours les règles 1 et 2 sauf si vous ne répondez pas aux spécifications du client et que vous êtes très familier avec l'ensemble du système, y compris la langue et les caractéristiques de mise en cache du processeur. À ce stade, suivez UNIQUEMENT la procédure en 3, ne pensez pas simplement "Hé, si je cache cette variable localement au lieu d'appeler un getter, les choses seront probablement plus rapides. Prouvez d'abord que ce n'est pas assez rapide, puis testez chaque optimisation séparément et jetez ceux qui n'aident pas. Documentez beaucoup tout au long du chemin.
Bill K
49

Je pense que cela devient tellement délicat que vous feriez mieux de faire tout ce qui rend le code plus lisible. À moins que vous n'effectuiez les opérations des milliers, voire des millions de fois, je doute que quiconque remarquera jamais la différence.

Si vous devez vraiment faire un choix, l'analyse comparative est la seule façon de procéder. Trouvez quelle (s) fonction (s) vous posent des problèmes, puis recherchez où dans la fonction les problèmes se produisent et corrigez ces sections. Cependant, je doute encore qu'une seule opération mathématique (même une opération répétée plusieurs, plusieurs fois) soit une cause de goulot d'étranglement.

Thomas Owens
la source
1
Lorsque je fabriquais des processeurs radar, une seule opération faisait la différence. Mais nous optimisions manuellement le code machine pour obtenir des performances en temps réel. Pour tout le reste, je vote pour simple et évident.
S.Lott
Je suppose que pour certaines choses, vous pourriez vous soucier d'une seule opération. Mais je m'attendrais à ce que dans 99% des applications, cela n'ait pas d'importance.
Thomas Owens
27
D'autant que l'OP cherchait une réponse en Python. Je doute que tout ce qui nécessite une telle efficacité soit écrit en Python.
Ed S.
4
Une division est probablement l'opération la plus coûteuse dans une routine d'intersection de triangle, qui est la base de la plupart des traceurs de rayons. Si vous stockez l'inverse et multipliez au lieu de diviser, vous ferez l'expérience d'accélération de nombreuses fois.
solinent le
@solinent - oui une accélération mais je doute "plusieurs fois" - la division et la multiplication en virgule flottante ne devraient pas être différentes de plus d'environ 4: 1, à moins que le processeur en question ne soit vraiment optimisé pour la multiplication et non la division.
Jason S
39

La multiplication est plus rapide, la division est plus précise. Vous perdrez de la précision si votre nombre n'est pas une puissance de 2:

y = x / 3.0;
y = x * 0.333333;  // how many 3's should there be, and how will the compiler round?

Même si vous laissez le compilateur déterminer la constante inversée avec une précision parfaite, la réponse peut toujours être différente.

x = 100.0;
x / 3.0 == x * (1.0/3.0)  // is false in the test I just performed

Le problème de vitesse n'a probablement d'importance que dans les langages C / C ++ ou JIT, et même dans ce cas uniquement si l'opération est dans une boucle à un goulot d'étranglement.

Mark Ransom
la source
La division est précise si vous divisez par des nombres entiers.
socle
7
La division en virgule flottante avec dénominateur> numérateur doit introduire des valeurs sans signification dans les bits de poids faible; la division réduit généralement la précision.
S.Lott
8
@ S.Lott: Non, ce n'est pas vrai. Toutes les implémentations en virgule flottante conformes à la norme IEEE-754 doivent arrondir parfaitement les résultats de chaque opération (c'est-à-dire au nombre à virgule flottante le plus proche) par rapport au mode d'arrondi actuel. Multiplier par la réciproque va toujours introduire plus d'erreur, du moins parce qu'un arrondi supplémentaire doit se produire.
Electro
1
Je sais que cette réponse a plus de 8 ans, mais elle est trompeuse; vous pouvez effectuer une division sans perte significative de précision: y = x * (1.0/3.0);et le compilateur calculera généralement 1/3 au moment de la compilation. Oui, 1/3 n'est pas parfaitement représentable dans IEEE-754, mais lorsque vous effectuez de l'arithmétique à virgule flottante, vous perdez de toute façon de la précision , que vous effectuiez une multiplication ou une division, car les bits de poids faible sont arrondis. Si vous savez que votre calcul est aussi sensible à l'erreur d'arrondi, vous devez également savoir comment traiter au mieux le problème.
Jason S
1
@JasonS Je viens de quitter un programme en cours d'exécution pendant la nuit, commençant à 1.0 et comptant par 1 ULP; J'ai comparé le résultat de la multiplication par (1.0/3.0)avec la division par 3.0. Je suis arrivé à 1.0000036666774155, et dans cet espace, 7,3% des résultats étaient différents. Je suppose qu'ils n'étaient différents que de 1 bit, mais comme l'arithmétique IEEE est garantie d'arrondir au résultat correct le plus proche, je maintiens ma déclaration selon laquelle la division est plus précise. Que la différence soit significative dépend de vous.
Mark Ransom
25

Si vous souhaitez optimiser votre code tout en restant clair, essayez ceci:

y = x * (1.0 / 2.0);

Le compilateur devrait être capable de faire la division au moment de la compilation, vous obtenez donc une multiplication au moment de l'exécution. Je m'attendrais à ce que la précision soit la même que dans le y = x / 2.0cas.

Là où cela peut avoir de l'importance, un LOT est dans les processeurs embarqués où une émulation en virgule flottante est nécessaire pour calculer l'arithmétique en virgule flottante.

Jason S
la source
12
Convenez à vous-même (et à quiconque l'a fait) - c'est une pratique courante dans le monde embarqué et les ingénieurs en logiciel dans ce domaine le trouvent clair.
Jason S
4
+1 pour être le seul ici à réaliser que les compilateurs ne peuvent pas optimiser les opérations en virgule flottante comme ils le souhaitent. Ils ne peuvent même pas changer l'ordre des opérandes dans une multiplication afin de garantir la précision (sauf s'il utilise un mode détendu).
rasmus le
1
OMG, il y a au moins 6 programmeurs qui pensent que les mathématiques élémentaires ne sont pas claires. AFAIK, la multiplication IEEE 754 est commutative (mais non associative).
maaartinus le
13
Peut-être que vous manquez le point. Cela n'a rien à voir avec l'exactitude algébrique. Dans un monde idéal, vous devriez simplement pouvoir diviser par deux:, y = x / 2.0;mais dans le monde réel, vous devrez peut-être cajoler le compilateur pour qu'il effectue une multiplication moins coûteuse. Il est peut-être moins clair pourquoi y = x * (1.0 / 2.0);c'est mieux, et il serait plus clair de le dire à la y = x * 0.5;place. Mais changez le 2.0en a 7.0et je préfère de beaucoup voir y = x * (1.0 / 7.0);que y = x * 0.142857142857;.
Jason S
3
Cela montre vraiment pourquoi il est plus lisible (et précis) d'utiliser votre méthode.
Juan Martinez
21

Je vais juste ajouter quelque chose pour l'option "autres langues".
C: Comme il ne s'agit que d'un exercice académique qui ne fait vraiment aucune différence, j'ai pensé que j'apporterais quelque chose de différent.

J'ai compilé en assemblage sans optimisations et j'ai regardé le résultat.
Le code:

int main() {

    volatile int a;
    volatile int b;

    asm("## 5/2\n");
    a = 5;
    a = a / 2;

    asm("## 5*0.5");
    b = 5;
    b = b * 0.5;

    asm("## done");

    return a + b;

}

compilé avec gcc tdiv.c -O1 -o tdiv.s -S

la division par 2:

movl    $5, -4(%ebp)
movl    -4(%ebp), %eax
movl    %eax, %edx
shrl    $31, %edx
addl    %edx, %eax
sarl    %eax
movl    %eax, -4(%ebp)

et la multiplication par 0,5:

movl    $5, -8(%ebp)
movl    -8(%ebp), %eax
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fmuls   LC0
fnstcw  -10(%ebp)
movzwl  -10(%ebp), %eax
orw $3072, %ax
movw    %ax, -12(%ebp)
fldcw   -12(%ebp)
fistpl  -16(%ebp)
fldcw   -10(%ebp)
movl    -16(%ebp), %eax
movl    %eax, -8(%ebp)

Cependant, lorsque j'ai changé ces ints en doubles (ce que ferait probablement python), j'ai obtenu ceci:

division:

flds    LC0
fstl    -8(%ebp)
fldl    -8(%ebp)
flds    LC1
fmul    %st, %st(1)
fxch    %st(1)
fstpl   -8(%ebp)
fxch    %st(1)

multiplication:

fstpl   -16(%ebp)
fldl    -16(%ebp)
fmulp   %st, %st(1)
fstpl   -16(%ebp)

Je n'ai évalué aucun de ce code, mais juste en examinant le code, vous pouvez voir qu'en utilisant des entiers, la division par 2 est plus courte que la multiplication par 2. En utilisant des doubles, la multiplication est plus courte car le compilateur utilise les opcodes en virgule flottante du processeur, qui probablement courir plus vite (mais en fait je ne sais pas) que de ne pas les utiliser pour la même opération. Donc en fin de compte, cette réponse a montré que la performance de la multiplication par 0,5 vs la division par 2 dépend de l'implémentation du langage et de la plateforme sur laquelle il fonctionne. Au final, la différence est négligeable et vous ne devriez pratiquement jamais vous inquiéter, sauf en termes de lisibilité.

En remarque, vous pouvez voir que dans mon programme main()renvoiea + b . Lorsque j'enlève le mot-clé volatile, vous ne devinerez jamais à quoi ressemble l'assemblage (à l'exclusion de la configuration du programme):

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

il a fait à la fois la division, la multiplication ET l'addition en une seule instruction! De toute évidence, vous n'avez pas à vous en soucier si l'optimiseur est respectable.

Désolé pour la réponse trop longue.

Carson Myers
la source
1
Ce n'est pas une "instruction unique". Il vient d'être constamment plié.
kvanberendonck
5
@kvanberendonck Bien sûr, c'est une seule instruction. Comptez-les: movl $5, %eax le nom de l'optimisation n'est pas important ni même pertinent. Vous vouliez juste être condescendant sur une réponse vieille de quatre ans.
Carson Myers le
2
La nature de l'optimisation est toujours importante à comprendre, car elle est contextuelle: elle ne s'applique que si vous ajoutez / multipliez / divisez / etc. constantes au moment de la compilation, où le compilateur peut simplement faire tous les calculs à l'avance et déplacer la réponse finale dans un registre lors de l'exécution. La division est beaucoup plus lente que la multiplication dans le cas général (diviseurs d'exécution), mais je suppose que la multiplication par des réciproques n'aide que si vous divisez par le même dénominateur plus d'une fois de toute façon. Vous savez probablement tout cela, mais les nouveaux programmeurs pourraient en avoir besoin, donc ... juste au cas où.
Mike S
10

Premièrement, à moins que vous ne travailliez en C ou en ASSEMBLAGE, vous êtes probablement dans un langage de niveau supérieur où les blocages de mémoire et les frais généraux d'appels éclipseront absolument la différence entre multiplier et diviser au point de ne plus être pertinent. Alors, choisissez simplement ce qui se lit mieux dans ce cas.

Si vous parlez à un niveau très élevé, ce ne sera pas beaucoup plus lent pour tout ce pour quoi vous êtes susceptible de l'utiliser. Vous verrez dans d'autres réponses, les gens doivent faire un million de multiplications / divisions juste pour mesurer une différence inférieure à la milliseconde entre les deux.

Si vous êtes toujours curieux, d'un point de vue d'optimisation de bas niveau:

Divide a tendance à avoir un pipeline beaucoup plus long que de se multiplier. Cela signifie qu'il faut plus de temps pour obtenir le résultat, mais si vous pouvez occuper le processeur avec des tâches non dépendantes, cela ne vous coûtera pas plus qu'une multiplication.

La durée de la différence de pipeline dépend entièrement du matériel. Le dernier matériel que j'ai utilisé était quelque chose comme 9 cycles pour une multiplication FPU et 50 cycles pour une division FPU. Cela semble beaucoup, mais vous perdriez alors 1000 cycles pour un manque de mémoire, ce qui peut mettre les choses en perspective.

Une analogie consiste à mettre une tarte dans un micro-ondes pendant que vous regardez une émission de télévision. Le temps total qu'il vous a éloigné de l'émission télévisée correspond au temps qu'il a fallu pour le mettre au micro-ondes et le sortir du micro-ondes. Le reste de votre temps, vous avez toujours regardé l'émission télévisée. Donc, si la tarte a mis 10 minutes à cuire au lieu d'une minute, elle n'a en fait plus utilisé votre temps à regarder la télévision.

En pratique, si vous voulez arriver au niveau de vous soucier de la différence entre Multiply et Divide, vous devez comprendre les pipelines, le cache, les blocages de branche, la prédiction dans le désordre et les dépendances de pipeline. Si cela ne correspond pas à votre intention d'aller avec cette question, la bonne réponse est d'ignorer la différence entre les deux.

Il y a de nombreuses (nombreuses) années, il était absolument essentiel d'éviter les divisions et d'utiliser toujours les multiplications, mais à l'époque, les coups de mémoire étaient moins pertinents et les fractures étaient bien pires. Ces jours-ci, j'évalue la lisibilité plus haut, mais s'il n'y a pas de différence de lisibilité, je pense que c'est une bonne habitude d'opter pour des multiplications.

James Podesta
la source
7

Écrivez celui qui indique le plus clairement votre intention.

Une fois que votre programme fonctionne, déterminez ce qui est lent et accélérez.

Ne faites pas l'inverse.

Jay Bazuzi
la source
6

Faites tout ce dont vous avez besoin. Pensez d'abord à votre lecteur, ne vous inquiétez pas des performances tant que vous n'êtes pas sûr d'avoir un problème de performances.

Laissez le compilateur faire les performances pour vous.

buti-oxa
la source
5

Si vous travaillez avec des entiers ou des types non flottants, n'oubliez pas vos opérateurs de décalage de bits: << >>

    int y = 10;
    y = y >> 1;
    Console.WriteLine("value halved: " + y);
    y = y << 1;
    Console.WriteLine("now value doubled: " + y);
sbeskur
la source
7
cette optimisation est automatiquement effectuée dans les coulisses de tout compilateur moderne.
Dustin Getz
Quelqu'un a-t-il testé si la vérification (en utilisant des opérations sur bits) si un opérande (?) A une version déplaçable pour l'utiliser à la place? function mul (a, b) {si (b vaut 2) renvoie a << 1; si (b vaut 4) renvoie a << 2; // ... etc retourne a * b; } Je suppose que le SI est si cher qu'il serait moins efficace.
Christopher Lightfoot
Cela n'a pas imprimé n'importe où près de ce que j'ai imaginé; Ça ne fait rien.
Christopher Lightfoot
Pour les opérations const, un compilateur normal doit faire le travail; mais ici, nous utilisons python, donc je ne suis pas sûr que ce soit assez intelligent pour le savoir? (Ça devrait être).
Christopher Lightfoot
Bon raccourci, sauf que ce qui se passe vraiment n'est pas immédiatement clair. La plupart des programmeurs ne reconnaissent même pas les opérateurs bitshift.
Blazemonger
4

En fait, il y a une bonne raison qu'en règle générale, la multiplication sera plus rapide que la division. La division en virgule flottante dans le matériel se fait soit avec des algorithmes de décalage et de soustraction conditionnelle ("division longue" avec des nombres binaires) ou - plus probablement de nos jours - avec des itérations comme l' algorithme de Goldschmidt . Le décalage et la soustraction nécessitent au moins un cycle par bit de précision (les itérations sont presque impossibles à paralléliser, tout comme le décalage et l'addition de multiplication), et les algorithmes itératifs effectuent au moins une multiplication par itération. Dans les deux cas, il est fort probable que la division prendra plus de cycles. Bien sûr, cela ne tient pas compte des bizarreries des compilateurs, du mouvement des données ou de la précision. En gros, cependant, si vous codez une boucle interne dans une partie sensible au temps d'un programme, écrire 0.5 * xou 1.0/2.0 * xplutôt que x / 2.0c'est une chose raisonnable à faire. Le pédantisme du "code ce qui est le plus clair" est absolument vrai, mais tous les trois sont si proches en lisibilité que le pédantisme est dans ce cas juste pédant.

Gène
la source
3

J'ai toujours appris que la multiplication est plus efficace.

Toon Krijthe
la source
«efficace» n'est pas le bon mot. Il est vrai que la plupart des processeurs se multiplient plus vite qu'ils ne se divisent. Cependant, avec les architectures modernes en pipeline, votre programme peut ne voir aucune différence. Comme beaucoup d' autres disent, vous êtes vraiment moyen mieux faire tout ce qui lit le mieux à un être humain.
TED
3

La multiplication est généralement plus rapide - certainement jamais plus lente. Cependant, s'il n'est pas critique pour la vitesse, écrivez ce qui est le plus clair.

Dan Hewett
la source
2

La division en virgule flottante est (généralement) particulièrement lente, donc si la multiplication en virgule flottante est également relativement lente, elle est probablement plus rapide que la division en virgule flottante.

Mais je suis plus enclin à répondre "ça n'a pas vraiment d'importance", à moins que le profilage n'ait montré que la division est un peu un goulot d'étranglement par rapport à la multiplication. J'imagine, cependant, que le choix de la multiplication par rapport à la division n'aura pas un impact important sur les performances de votre application.

mipadi
la source
2

Cela devient plus une question lorsque vous programmez en assemblage ou peut-être en C. Je suppose qu'avec la plupart des langages modernes, cette optimisation est faite pour moi.

Seamus
la source
2

Méfiez-vous de "deviner que la multiplication est généralement préférable, alors j'essaie de m'en tenir à cela lorsque je code",

Dans le contexte de cette question spécifique, mieux signifie ici «plus vite». Ce qui n'est pas très utile.

Penser à la vitesse peut être une grave erreur. Il y a de profondes implications d'erreur dans la forme algébrique spécifique du calcul.

Voir Arithmétique à virgule flottante avec analyse d'erreur . Voir Problèmes de base dans l'arithmétique à virgule flottante et l'analyse des erreurs .

Alors que certaines valeurs à virgule flottante sont exactes, la plupart des valeurs à virgule flottante sont une approximation; ce sont une valeur idéale plus une erreur. Chaque opération s'applique à la valeur idéale et à la valeur d'erreur.

Les plus gros problèmes viennent d'essayer de manipuler deux nombres presque égaux. Les bits les plus à droite (les bits d'erreur) en viennent à dominer les résultats.

>>> for i in range(7):
...     a=1/(10.0**i)
...     b=(1/10.0)**i
...     print i, a, b, a-b
... 
0 1.0 1.0 0.0
1 0.1 0.1 0.0
2 0.01 0.01 -1.73472347598e-18
3 0.001 0.001 -2.16840434497e-19
4 0.0001 0.0001 -1.35525271561e-20
5 1e-05 1e-05 -1.69406589451e-21
6 1e-06 1e-06 -4.23516473627e-22

Dans cet exemple, vous pouvez voir que lorsque les valeurs diminuent, la différence entre des nombres presque égaux crée des résultats différents de zéro où la réponse correcte est zéro.

S.Lott
la source
1

J'ai lu quelque part que la multiplication est plus efficace en C / C ++; Aucune idée concernant les langues interprétées - la différence est probablement négligeable en raison de tous les autres frais généraux.

À moins que cela ne devienne un problème, restez fidèle à ce qui est plus maintenable / lisible - je déteste quand les gens me disent cela, mais c'est tellement vrai.

Christopher Lightfoot
la source
1

Je suggérerais la multiplication en général, car vous n'avez pas à passer les cycles à vous assurer que votre diviseur n'est pas 0. Cela ne s'applique pas, bien sûr, si votre diviseur est une constante.

Steve
la source
1

Java Android, profilé sur Samsung GT-S5830

public void Mutiplication()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a *= 0.5f;
    }
}
public void Division()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a /= 2.0f;
    }
}

Résultats?

Multiplications():   time/call: 1524.375 ms
Division():          time/call: 1220.003 ms

La division est environ 20% plus rapide que la multiplication (!)

PiotrK
la source
1
Pour être réaliste, vous devez tester a = i*0.5, non a *= 0.5. C'est ainsi que la plupart des programmeurs utiliseront les opérations.
Blazemonger
1

Comme pour les articles # 24 (la multiplication est plus rapide) et # 30 - mais parfois ils sont tous les deux tout aussi faciles à comprendre:

1*1e-6F;

1/1e6F;

~ Je les trouve tous les deux tout aussi faciles à lire et je dois les répéter des milliards de fois. Il est donc utile de savoir que la multiplication est généralement plus rapide.

Chris
la source
1

Il y a une différence, mais elle dépend du compilateur. Au début, sur vs2003 (c ++), je n'ai eu aucune différence significative pour les types doubles (virgule flottante 64 bits). Cependant, en exécutant à nouveau les tests sur vs2010, j'ai détecté une énorme différence, jusqu'à un facteur 4 plus rapide pour les multiplications. En suivant cela, il semble que vs2003 et vs2010 génèrent un code fpu différent.

Sur un Pentium 4, 2,8 GHz, par rapport à 2003:

  • Multiplication: 8,09
  • Division: 7,97

Sur un Xeon W3530, par rapport à 2003:

  • Multiplication: 4,68
  • Division: 4,64

Sur un Xeon W3530, vs2010:

  • Multiplication: 5,33
  • Division: 21.05

Il semble que sur vs2003 une division dans une boucle (donc le diviseur a été utilisé plusieurs fois) a été traduite en une multiplication avec l'inverse. Sur vs2010, cette optimisation n'est plus appliquée (je suppose car il y a un résultat légèrement différent entre les deux méthodes). Notez également que le processeur effectue des divisions plus rapidement dès que votre numérateur est 0,0. Je ne connais pas l'algorithme précis câblé dans la puce, mais peut-être que cela dépend du nombre.

Edit 18-03-2013: l'observation pour vs2010

gast128
la source
Je me demande s'il y a une raison pour laquelle un compilateur ne peut pas remplacer, par exemple n/10.0par une expression du formulaire (n * c1 + n * c2)? Je m'attendrais à ce que sur la plupart des processeurs, une division prenne plus de deux multiplications et une division, et je crois que la division par n'importe quelle constante peut donner un résultat correctement arrondi dans tous les cas en utilisant la formulation indiquée.
supercat
1

Voici une réponse idiote et amusante:

x / 2.0 n'est pas équivalent à x * 0.5

Disons que vous avez écrit cette méthode le 22 octobre 2008.

double half(double x) => x / 2.0;

Maintenant, 10 ans plus tard, vous apprenez que vous pouvez optimiser ce morceau de code. La méthode est référencée dans des centaines de formules à travers votre application. Vous le changez donc et bénéficiez d'une amélioration remarquable des performances de 5%.

double half(double x) => x * 0.5;

Était-ce la bonne décision de changer le code? En maths, les deux expressions sont en effet équivalentes. En informatique, cela n'est pas toujours vrai. Veuillez lire Minimiser l'effet des problèmes de précision pour plus de détails. Si vos valeurs calculées sont - à un moment donné - comparées à d'autres valeurs, vous modifierez le résultat des cas extrêmes. Par exemple:

double quantize(double x)
{
    if (half(x) > threshold))
        return 1;
    else
        return -1;
}

L'essentiel est; une fois que vous vous êtes contenté de l'un des deux, respectez-le!

l33t
la source
1
Un vote négatif? Que diriez-vous d'un commentaire expliquant vos pensées? Cette réponse est certainement pertinente à 100%.
l33t
En informatique, multiplier / diviser les valeurs en virgule flottante par des puissances de 2 est sans perte, sauf si la valeur devient dénormalisée ou déborde.
Soonts
Étant donné que la virgule flottante n'est pas sans perte au moment de la division, peu importe si votre déclaration est vraie. Bien que je serais très surpris si c'était le cas.
l33t
1
«La virgule flottante n'est pas sans perte au moment de la division» uniquement lorsque vous construisez avec un ancien compilateur qui émet du code x87 obsolète. Sur le matériel moderne, le simple fait d'avoir une variable flottante / double est sans perte, 32 ou 64 bits IEEE 754: en.wikipedia.org/wiki/IEEE_754 En raison du fonctionnement de l'IEEE 754, lorsque vous divisez par 2 ou multipliez par 0,5, vous diminuez l'exposant par 1, le reste des bits (signe + mantisse) ne change pas. Et les deux 2et les 0.5nombres peuvent être représentés exactement dans IEEE 754, sans aucune perte de précision (contrairement par exemple 0.4ou 0.1, ils ne peuvent pas).
Soonts
0

Eh bien, si nous supposons qu'une opération d'ajout / sous-piste coûte 1, multipliez les coûts par 5 et divisez les coûts par 20.

matma
la source
D'où avez-vous obtenu ces chiffres? expérience? à l'instinct, viscéral? article sur Internet? comment changeraient-ils pour différents types de données?
kroiz le
0

Après une discussion aussi longue et intéressante, voici mon point de vue sur ceci: Il n'y a pas de réponse définitive à cette question. Comme certains l'ont souligné, cela dépend à la fois du matériel (cf. piotrk et gast128 ) et du compilateur (cf. les tests de @Javier ). Si la vitesse n'est pas critique, si votre application n'a pas besoin de traiter en temps réel une énorme quantité de données, vous pouvez opter pour la clarté en utilisant une division, alors que si la vitesse de traitement ou la charge du processeur sont un problème, la multiplication peut être la plus sûre. Enfin, à moins que vous ne sachiez exactement sur quelle plateforme votre application sera déployée, le benchmark n'a pas de sens. Et pour la clarté du code, un seul commentaire ferait l'affaire!

Jean-François
la source
-3

Techniquement, la division n'existe pas, il y a juste une multiplication par éléments inverses. Par exemple, vous ne divisez jamais par 2, vous multipliez en fait par 0,5.

La «division» - disons-nous qu'elle existe pendant une seconde - est toujours plus difficile que la multiplication parce que pour «diviser» xpar yun il faut d'abord calculer la valeur y^{-1}telle que y*y^{-1} = 1puis faire la multiplication x*y^{-1}. Si vous le savez déjà, ne y^{-1}pas le calculer à partir de ydoit être une optimisation.

satnhak
la source
3
Ce qui ignore complètement la réalité des deux commandes existant dans le silicium.
NPSF3000
@ NPSF3000 - Je ne suis pas. Sous l'hypothèse que les deux opérations existent, il affirme simplement que l'opération de division implique implicitement le calcul d'un inverse multiplicatif et d'une multiplication, ce qui sera toujours plus difficile qu'une simple multiplication. Le silicium est un détail de mise en œuvre.
satnhak
@ BTyler. Si les deux commandes existent dans le silicium, et que les deux commandes prennent le même nombre de cycles [comme on pourrait s'y attendre], la complexité relative des instructions n'est pas du tout pertinente d'un point de vue de performance.
NPSF3000
@ NPSF3000 - mais ils ne prennent pas tous les deux le même nombre de cycles parce que la multiplication est plus rapide.
satnhak