web-dev-qa-db-fra.com

Devrais-je utiliser la multiplication ou la division?

Voici une question amusante stupide:

Disons que nous devons effectuer une opération simple où nous avons besoin de la moitié de la valeur d'une variable. Il y a typiquement deux manières de le faire:

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

En supposant que nous utilisons les opérateurs standard fournis avec le langage, lequel est le plus performant?

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

Bien que personnellement, la réponse pour Python 2.4-2.5 m'intéresse, n'hésitez pas à poster é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).

108
Edmundito

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 se diviser, mais à mesure que vous vous rapprochez du processeur en utilisant des machines virtuelles ou JIT plus avancées, l'avantage disparaît. Il est tout à fait possible qu'un futur Python VM le rende inutile

73
Javier

Toujours utiliser ce qui est le plus clair. Tout ce que vous faites, c’est essayer de déjouer le compilateur. Si le compilateur est intelligent, il fera de son mieux pour optimiser le résultat, mais rien ne peut empêcher le prochain gars de vous haïr pour votre solution merdique bitshifting (j'aime la manipulation de bits en passant, c'est amusant. Mais amusant! = Lisible )

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

  1. Ne pas optimiser.
  2. Si vous êtes un expert, reportez-vous à la règle 1
  3. Si vous êtes un expert et pouvez justifier le besoin, utilisez la procédure suivante:

    • Codez-le non optimisé
    • déterminer la rapidité du "assez rapide" - Notez les besoins/histoires de l'utilisateur nécessitant cette métrique.
    • Écrire un test de vitesse
    • Testez le code existant - Si c'est assez rapide, vous avez terminé.
    • Recodez-le optimisé
    • Testez le code optimisé. SI ce n'est pas le cas, jetez-le et conservez l'original.
    • Si cela répond au test, conservez le code d'origine sous forme de commentaires

En outre, vous pouvez, par exemple, supprimer des boucles internes lorsqu'elles ne sont pas nécessaires ou choisir une liste liée plutôt qu'un tableau pour un tri par insertion, ce ne sont pas des optimisations, mais simplement de la programmation.

62
Bill K

Je pense que cela devient tellement tatillon 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 remarque jamais la différence.

Si vous devez vraiment faire votre choix, le benchmarking est la seule solution. Recherchez la ou les fonctions qui vous posent des problèmes, puis recherchez leur emplacement dans la fonction, puis corrigez ces sections. Cependant, je doute toujours qu'une seule opération mathématique (même une répétée plusieurs fois) puisse être à l'origine d'un goulot d'étranglement.

47
Thomas Owens

La multiplication est plus rapide, la division est plus précise. Vous perdrez un peu de 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

La question de la vitesse n’est susceptible d’importer que dans les langages C/C++ ou JIT, et même dans ce cas uniquement si l’opération est en boucle à un goulot d’étranglement.

33
Mark Ransom

Si vous voulez 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, afin que vous obteniez une multiplication au moment de l'exécution. Je m'attendrais à ce que la précision soit la même que dans le cas y = x / 2.0.

Lorsque cela est important, beaucoup se trouvent dans les processeurs intégrés où une émulation en virgule flottante est nécessaire pour calculer l'arithmétique en virgule flottante.

24
Jason S

Je vais juste ajouter quelque chose pour l'option "autres langues".
C: Comme il s’agit simplement d’un exercice académique qui vraiment ne fait aucune différence, je pensais apporter quelque chose de différent.

J'ai compilé à l'Assemblée sans optimisation et 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, quand j'ai changé ces ints en doubles (ce que python ferait probablement), j'ai eu 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 référencé aucun de ce code, mais juste en l'examinant, vous pouvez voir que, avec les entiers, la division par 2 est plus courte que la multiplication par 2. En utilisant les doublons, la multiplication est plus courte, car le compilateur utilise les opcodes à virgule flottante du processeur, probablement plus vite (mais en fait je ne sais pas) que de ne pas les utiliser pour la même opération. Donc, finalement, cette réponse a montré que la performance de multiplication de 0,5 par rapport à la division de 2 dépend de la mise en œuvre du langage et de la plate-forme sur laquelle il s'exécute. En fin de compte, la différence est négligeable et vous ne devriez pratiquement jamais vous en soucier, sauf en termes de lisibilité.

En remarque, vous pouvez voir que dans mon programme main() renvoie a + b. Lorsque je supprime le mot clé volatile, vous ne pouvez jamais deviner à quoi ressemble l'Assemblée (à l'exception de la configuration du programme):

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

il a fait la division, la multiplication ET l'addition en une seule instruction! Il est clair que vous n’aurez pas à vous en préoccuper si l’optimiseur est respectable.

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

19
Carson Myers

Premièrement, à moins que vous ne travailliez dans C ou Assembly, vous êtes probablement dans un langage de niveau supérieur où la mémoire est bloquée et les frais généraux des appels vont absolument réduire la différence entre multiplier et diviser au point de ne plus avoir aucune pertinence. Alors, choisissez ce qui se lit le mieux dans ce cas.

Si vous parlez d'un niveau très élevé, il ne sera pas beaucoup plus lent pour quoi que ce soit pour lequel vous l'utiliserez. Vous verrez dans d'autres réponses que les gens doivent multiplier/diviser un million simplement 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:

La division a tendance à avoir un pipeline beaucoup plus long que la multiplication. Cela signifie qu'il faut plus de temps pour obtenir le résultat, mais si vous pouvez garder le processeur occupé avec des tâches non dépendantes, cela ne vous coûtera finalement rien de plus qu'une multiplication.

La longueur de la différence de pipeline dépend entièrement du matériel. Le dernier matériel 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 une mémoire manquante, ce qui peut mettre les choses en perspective.

Une analogie consiste à placer une tarte dans un four micro-ondes pendant que vous regardez une émission de télévision. Le temps total écoulé depuis l’émission de télévision est le temps qu’il a fallu pour le mettre au micro-ondes et le sortir du micro-ondes. Le reste de votre temps, vous regardiez encore l'émission de télévision. Donc, si la tarte prenait 10 minutes à cuire au lieu de 1 minute, elle n’utilisait pas plus de votre temps à regarder la télévision.

En pratique, si vous voulez en venir au degré de différence entre Multiplier et Diviser, vous devez comprendre les pipelines, le cache, les décrochages de branche, la prédiction hors service et les dépendances de pipeline. Si cela ne vous ressemble pas, la bonne réponse est d'ignorer la différence entre les deux. 

Il y a de cela (plusieurs) années, il était absolument essentiel d'éviter les divisions et de toujours utiliser des multiplications, mais à l'époque, les succès en matière de mémoire étaient moins pertinents et les divisions beaucoup plus graves. De nos jours, je privilégie la lisibilité, mais s’il n’ya pas de différence de lisibilité, je pense que c’est une bonne habitude d’opter pour des multiplications.

9
James Podesta

Écrivez ce qui est plus clairement énonce votre intention.

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

Ne faites pas l'inverse.

8
Jay Bazuzi

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

Laissez le compilateur faire la performance pour vous.

7
buti-oxa

Si vous travaillez avec des entiers ou des types à virgule non flottante, n'oubliez pas vos opérateurs de bitshifting: << >> 

    int y = 10;
    y = y >> 1;
    Console.WriteLine("value halved: " + y);
    y = y << 1;
    Console.WriteLine("now value doubled: " + y);
4
sbeskur

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

4
Dan Hewett

En fait, il y a une bonne raison pour qu'en règle générale, la multiplication soit plus rapide que la division. La division en virgule flottante dans le matériel se fait soit avec des algorithmes de soustraction à décalage et conditionnels ("division longue" avec des nombres binaires), soit - plus probablement de nos jours - avec des itérations comme celle de de Goldschmidt . Décaler et soustraire nécessite au moins un cycle par bit de précision (les itérations sont presque impossibles à paralléliser, de même que le décalage et l'ajout 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 entendu, cela ne tient pas compte des bizarreries dans les compilateurs, du mouvement des données ou de la précision. De manière générale, cependant, si vous codez une boucle interne dans une partie du programme qui dépend du temps, écrire 0.5 * x ou 1.0/2.0 * x plutôt que x / 2.0 est une chose raisonnable à faire. Le pédantisme de "code ce qui est le plus clair" est tout à fait vrai, mais leur lisibilité est si étroite que le pédantisme est dans ce cas simplement pédant.

4
Gene

La division en virgule flottante est (généralement) particulièrement lente. Ainsi, 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 "cela n'a pas vraiment d'importance", à moins que le profilage ne montre que la division est un peu 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.

3
mipadi

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

2
Seamus

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

2
Toon Krijthe

Méfiez-vous: "deviner la multiplication est généralement préférable, donc j'essaie de m'en tenir à cela quand 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 existe de profondes implications d'erreur dans la forme algébrique spécifique du calcul. 

Voir Arithmétique en virgule flottante avec analyse des erreurs . Voir Problèmes de base liés à l’arithmétique en virgule flottante et à l’analyse des erreurs

Bien que certaines valeurs à virgule flottante soient exactes, la plupart des valeurs à virgule flottante sont approximatives; ils 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 proviennent de la manipulation de deux nombres presque égaux. Les bits les plus à droite (les bits d'erreur) finissent par 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 deviennent plus petites, la différence entre des nombres presque égaux crée des résultats non nuls pour lesquels la réponse correcte est zéro.

2
S.Lott

Il y a une différence, mais cela dépend du compilateur. Au début sur vs2003 (c ++), je n’ai 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 différence énorme, le facteur 4 étant plus rapide pour les multiplications. En traquant cela, il semble que vs2003 et vs2010 génèrent un code fpu différent.

Sur un Pentium 4, 2,8 GHz, vs2003:

  • Multiplication: 8.09
  • Division: 7,97

Sur un Xeon W3530, vs2003:

  • 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 (le diviseur a donc é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 les 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 dépend-il du nombre.

18-03-2013: l'observation pour vs2010

1
gast128

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 (!)

1
PiotrK

Comme avec les posts # 24 (la multiplication est plus rapide) et # 30 - mais parfois, ils sont tout aussi faciles à comprendre:

1*1e-6F;

1/1e6F;

~ Je les trouve 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.

1
Chris

Voici une réponse amusante et ridicule:

x/2.0 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;

Dix ans plus tard, vous apprenez qu'il est possible d'optimiser ce code. La méthode est référencée dans des centaines de formules dans votre application. Donc, vous le changez et vous constatez une amélioration remarquable de 5% des performances.

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

Était-ce la bonne décision de changer le code? En mathématiques, les deux expressions sont effectivement équivalentes. En informatique, cela n’est pas toujours vrai. Veuillez lire Réduire au minimum 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 Edge. Par exemple.:

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

La ligne du bas est; Une fois que vous avez choisi l'un ou l'autre, tenez-vous-en!

1
l33t

J'ai lu quelque part que la multiplication est plus efficace en C/C++; Aucune idée concernant les langages interprétés - 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 concentré sur ce qui est plus facile à maintenir/à lire - je déteste ça quand les gens me le disent, mais c’est tellement vrai.

1

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

1
Steve

Après une discussion aussi longue et intéressante, voici ce que je pense: Il n’ya pas de réponse définitive à cette question. Comme certaines personnes l'ont souligné, cela dépend à la fois du matériel (cf piotrk et gast128 ) et du compilateur (voir 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 quantité énorme de données, vous pouvez opter pour la clarté en utilisant une division. Si la vitesse de traitement ou la charge du processeur posent problème, la multiplication peut être la solution la plus sûre .. Enfin, à moins que vous ne sachiez exactement sur quelle plate-forme 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! 

0
Jean-François

Eh bien, si nous supposons qu’une opération d’addition/soustraction coûte 1, multiplie ensuite 5 et divise environ 20.

0
matma