2009-12-24 3 views
3

En optimisation de méditée code, je me demandais ce qui était plus cher en python:Python si les autres micro-optimisation

if x: 
    d = 1 
else: 
    d = 2 

ou

d = 2 
if x: 
    d = 1 

Toute pensée? J'aime le nombre de lignes réduit dans la seconde, mais je me demandais si la réaffectation était plus coûteuse que la commutation de condition.

+10

Lorsque vous les avez comparés avec le temps, qu'avez-vous appris? S'il vous plaît ne demandez pas sur l'optimisation sans temps d'exécution. –

+0

Merci, je n'étais pas au courant de timeit, je viens de le trouver et je construis un test de temps. –

+1

-1 pour la micro-optimisation spéculative –

Répondre

20

Ne pas réfléchir, ne vous étonnez pas, mesure - avec timeità la coquille ligne de commande (de loin la meilleure, la façon la plus simple de l'utiliser!). Python 2.5.4 sur Mac OS X 10.5 sur un ordinateur portable ...:

$ python -mtimeit -s'x=0' 'if x: d=1' 'else: d=2' 
10000000 loops, best of 3: 0.0748 usec per loop 
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2' 
10000000 loops, best of 3: 0.0685 usec per loop 
$ python -mtimeit -s'x=0' 'd=2' 'if x: d=1' 
10000000 loops, best of 3: 0.0734 usec per loop 
$ python -mtimeit -s'x=1' 'd=2' 'if x: d=1' 
10000000 loops, best of 3: 0.101 usec per loop 

Vous voyez: le « juste si » forme peut sauver 1,4 nanosecondes lorsque x est faux, mais coûte 40,2 nanosecondes lorsque x est vrai , par rapport au formulaire "if/else"; donc, dans un contexte de micro-optimisation, vous devriez utiliser le premier seulement si x est 30 fois plus susceptible d'être faux que vrai, ou à peu près. Aussi:

$ python -mtimeit -s'x=0' 'd=1 if x else 2' 
10000000 loops, best of 3: 0.0736 usec per loop 
$ python -mtimeit -s'x=1' 'd=1 if x else 2' 
10000000 loops, best of 3: 0.076 usec per loop 

... l'opérateur ternaire du if/else a ses propres plus et moins minuscules. Lorsque les différences sont aussi minimes, vous devez mesurer à plusieurs reprises, déterminer le niveau de bruit et vous assurer de ne pas prendre en compte les différences «dans le bruit». Par exemple, pour comparer la déclaration vs expression if/else dans le « x est vrai » cas, répéter chacun plusieurs fois:

$ python -mtimeit -s'x=1' 'd=1 if x else 2' 
10000000 loops, best of 3: 0.076 usec per loop 
$ python -mtimeit -s'x=1' 'd=1 if x else 2' 
10000000 loops, best of 3: 0.0749 usec per loop 
$ python -mtimeit -s'x=1' 'd=1 if x else 2' 
10000000 loops, best of 3: 0.0742 usec per loop 
$ python -mtimeit -s'x=1' 'd=1 if x else 2' 
10000000 loops, best of 3: 0.0749 usec per loop 
$ python -mtimeit -s'x=1' 'd=1 if x else 2' 
10000000 loops, best of 3: 0.0745 usec per loop 

vous pouvez maintenant affirmer que les formes d'expression prend (sur cette machine et les versions de clé logiciel) 74,2 à 76,0 nanosecondes - la gamme est beaucoup plus expressive que n'importe quel nombre unique serait. Et de même:

$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2' 
10000000 loops, best of 3: 0.0688 usec per loop 
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2' 
10000000 loops, best of 3: 0.0681 usec per loop 
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2' 
10000000 loops, best of 3: 0.0687 usec per loop 
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2' 
10000000 loops, best of 3: 0.0679 usec per loop 
$ python -mtimeit -s'x=1' 'if x: d=1' 'else: d=2' 
10000000 loops, best of 3: 0.0692 usec per loop 

maintenant vous pouvez affirmer avec confiance que le formulaire de déclaration prend (dans des conditions identiques) 67,9 à 69,2 nanosecondes; donc son avantage, pour x vrai, par rapport à la forme d'expression, est de 4,8 à 8,1 nanosecondes (il est juste de limiter cette estimation à 6,3 à 6,8 nanosecondes, comparant min/min et max/max au lieu de min/max et max/min que l'estimation plus large, plus prudentielle fait).

Combien de temps et d'énergie vaut la peine de consacrer à ces différences microscopiques, une fois que vous avez réalisé pour un soin donné qu'ils sont microscopique, est, bien sûr, un problème différent.

+3

SuperMartelli à la rescousse! +1 pour un bloc géant de benchmarks. –

+1

J'ai remarqué que certaines personnes semblent avoir plus de plaisir à donner des conférences sur ce qu'elles devraient faire, mais d'autres ont plus de plaisir à nous éduquer sur les points les plus fins. Merci, Alex. –

2

Le second devrait évidemment être plus cher, il fait les mêmes opérations si x est faux et deux fois les affectations si x est vrai. Hypothèse: l'assignation est plus chère en python qu'un saut conditionnel, ce qui est logique car il est interprété et il doit lire un hash d'exécution pour obtenir la nouvelle valeur puis la télécharger dans le même hash.

+1

Je pense que vous pouvez complètement ignorer Python, l'assignation est presque garantie d'être plus lente que les sauts conditionnels même dans le code machine nu. À moins que vos fonctions de comparaison ne soient vraiment désagréables (ce n'est pas le cas pour les nombres entiers, évidemment), la première forme devrait presque toujours être supérieure, en supposant que les conditions testées ont toutes les deux une chance raisonnable de se produire. –

+2

Lorsque vous êtes confronté à un effacement de cache dû à des sauts conditionnels, rien n'est aussi simple qu'il n'y paraît. Mais pour Python en particulier, cela semble être un bon pari. – Blindy

+0

@Nicholas: Dans asm, pour des objets minuscules comme un entier 32bit qui tient dans un registre, l'affectation est l'opération la moins chère possible. Sur x86, 'mov reg, imm32' ou surtout' mov reg, reg' sont extrêmement bon marché. Le second [n'a même pas besoin d'une unité d'exécution] (http://agner.org/optimize/) sur AMD moderne (famille Bulldozer) ou Intel (famille SnB). Une branche conditionnelle ('cmp' /' jcc') est également assez bon marché lorsqu'elle prédit correctement, mais le cas pris en charge affecte encore l'instruction-fetch. Un malfaiteur coûte ~ 50 fois un 'mov'. 'mov [mem], reg' est moins cher qu'une branche conditionnelle sauf si elle manque dans le cache –

5

Vous devriez probablement référence, mais il y a aussi une troisième forme qui utilise l'opérateur ternaire:

d = 1 if x else 2 
+2

' d = 2 - bool (x) ' –

+0

qui ne fonctionne que pour le cas 1 contre 2, si vous voulez 15 et 31, il doesn Ne travaillez pas –

+2

Ewww, Chris, ça m'a littéralement fait mal. :( –

1

Je dirais que celui qui est le plus lisible est le plus optimisé (pour la lisibilité au moins).

La construction if ... else indique clairement que vous avez affaire à un cas/ou. La construction d'assignation pourrait avoir plus de sens si (d == 2) est la valeur habituelle et votre si les tests pour un cas inhabituel. Cette construction devient moins claire si votre mission est éloignée du si.

Dans cet exemple simple, cela n'a pas vraiment d'importance. Pour un code plus complexe, j'optimiserais presque toujours pour la lisibilité, même au prix de quelques cycles de CPU.

+0

L'une des principales philosophies de Python est "le code est écrit une fois, mais lu plusieurs fois". Si vous commencez à envisager des optimisations ridiculement triviales comme celle-ci, votre temps serait mieux de réécrire cette partie du module en tant qu'extension C, et de garder votre code Python agréable et lisible. '2-bool (x)' pourrait être 1,2 nanosecondes plus rapide, mais c'est beaucoup moins évident que 'si x: d = 1', et plus probablement plus lent que 'if (x) {d = 1;}' – dbr

Questions connexes