Je parcourais Google Code lorsque je suis tombé sur ce projet appelé JSpeed - optimisation pour Javascript.
J'ai remarqué que l'une des optimisations consistait à modifier i++
à ++i
in pour les instructions de boucle.
Avant optimisation
for (i=0;i<1;i++) {}
for (var i = 0, j = 0; i < 1000000; i++, j++) {
if (i == 4) {
var tmp = i / 2;
}
if ((i % 2) == 0) {
var tmp = i / 2;
i++;
}
}
var arr = new Array(1000000);
for (i = 0; i < arr.length; i++) {}
Après optimisation
for(var i=0;i<1;++i){}
for(var i=0,j=0;i<1000000;++i,++j){if(i==4){var tmp=i>>1;}
if((i&1)==0){var tmp=i>>1;i++;}}
var arr=new Array(1000000);for(var i=0,arr_len=arr.length;i<arr_len;++i){}
Je sais ce que font les incréments pré et post, mais vous savez comment cela accélère le code?
Voici ce que j'ai lu et je pourrais répondre à votre question: "preincrement (++i
) ajoute un à la valeur de i
, puis renvoie i
; en revanche, i++
renvoie i
puis en ajoute un, ce qui en théorie entraîne la création d'une variable temporaire stockant la valeur de i
avant d'appliquer l'opération d'incrémentation " .
Il s'agit d'une fausse optimisation. Pour autant que je le comprends, vous enregistrez 1 code op. Si vous cherchez à optimiser votre code avec cette technique, vous avez fait le mauvais choix. De plus, la plupart des compilateurs/interprètes optimiseront cela pour vous de toute façon ( référence 1 ). En bref, je ne m'inquiéterais pas. Mais, si vous êtes vraiment inquiet, vous devriez utiliser i+=1
.
Voici la référence rapide et sale que je viens de faire
var MAX = 1000000, t=0,i=0;
t = (new Date()).getTime();
for ( i=0; i<MAX;i++ ) {}
t = (new Date()).getTime() - t;
console.log(t);
t = (new Date()).getTime();
for ( i=0; i<MAX;++i ) {}
t = (new Date()).getTime() - t;
console.log(t);
t = (new Date()).getTime();
for ( i=0; i<MAX;i+=1 ) {}
t = (new Date()).getTime() - t;
console.log(t);
Résultats bruts
Post Pre +=
1071 1073 1060
1065 1048 1051
1070 1065 1060
1090 1070 1060
1070 1063 1068
1066 1060 1064
1053 1063 1054
Supprimé le plus bas et le plus haut
Post Pre +=
1071 ---- 1060
1065 ---- ----
1070 1065 1060
---- 1070 1060
1070 1063 ----
1066 1060 1064
---- 1063 1054
Moyennes
1068.4 1064.2 1059.6
Notez que cela dépasse un million d'itérations et que les résultats sont en moyenne 9 millisecondes. Pas vraiment une optimisation étant donné que la plupart des traitements itératifs en JavaScript sont effectués sur des ensembles beaucoup plus petits (conteneurs DOM par exemple).
En théorie, l'utilisation d'un opérateur post-incrémentation peut produire un temporaire. En pratique, les compilateurs JavaScript sont suffisamment intelligents pour éviter cela, en particulier dans ce cas trivial.
Par exemple, considérons cet exemple de code:
sh$ cat test.js
function preInc(){
for(i=0; i < 10; ++i)
console.log(i);
}
function postInc(){
for(i=0; i < 10; i++)
console.log(i);
}
// force lazy compilation
preInc();
postInc();
Dans ce cas, le compilateur V8 dans NodeJS produit exactement le même bytecode (regardez en particulier les opcodes 39-44 pour l'incrément):
sh$ node --version
v8.9.4
sh$ node --print-bytecode test.js | sed -nEe '/(pre|post)Inc/,/^\[/p'
[generating bytecode for function: preInc]
Parameter count 1
Frame size 24
77 E> 0x1d4ea44cdad6 @ 0 : 91 StackCheck
87 S> 0x1d4ea44cdad7 @ 1 : 02 LdaZero
88 E> 0x1d4ea44cdad8 @ 2 : 0c 00 03 StaGlobalSloppy [0], [3]
94 S> 0x1d4ea44cdadb @ 5 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44cdade @ 8 : 1e fa Star r0
0x1d4ea44cdae0 @ 10 : 03 0a LdaSmi [10]
94 E> 0x1d4ea44cdae2 @ 12 : 5b fa 07 TestLessThan r0, [7]
0x1d4ea44cdae5 @ 15 : 86 23 JumpIfFalse [35] (0x1d4ea44cdb08 @ 50)
83 E> 0x1d4ea44cdae7 @ 17 : 91 StackCheck
109 S> 0x1d4ea44cdae8 @ 18 : 0a 01 0d LdaGlobal [1], [13]
0x1d4ea44cdaeb @ 21 : 1e f9 Star r1
117 E> 0x1d4ea44cdaed @ 23 : 20 f9 02 0f LdaNamedProperty r1, [2], [15]
0x1d4ea44cdaf1 @ 27 : 1e fa Star r0
121 E> 0x1d4ea44cdaf3 @ 29 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44cdaf6 @ 32 : 1e f8 Star r2
117 E> 0x1d4ea44cdaf8 @ 34 : 4c fa f9 f8 0b CallProperty1 r0, r1, r2, [11]
102 S> 0x1d4ea44cdafd @ 39 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44cdb00 @ 42 : 41 0a Inc [10]
102 E> 0x1d4ea44cdb02 @ 44 : 0c 00 08 StaGlobalSloppy [0], [8]
0x1d4ea44cdb05 @ 47 : 77 2a 00 JumpLoop [42], [0] (0x1d4ea44cdadb @ 5)
0x1d4ea44cdb08 @ 50 : 04 LdaUndefined
125 S> 0x1d4ea44cdb09 @ 51 : 95 Return
Constant pool (size = 3)
Handler Table (size = 16)
[generating bytecode for function: get]
[generating bytecode for function: postInc]
Parameter count 1
Frame size 24
144 E> 0x1d4ea44d821e @ 0 : 91 StackCheck
154 S> 0x1d4ea44d821f @ 1 : 02 LdaZero
155 E> 0x1d4ea44d8220 @ 2 : 0c 00 03 StaGlobalSloppy [0], [3]
161 S> 0x1d4ea44d8223 @ 5 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44d8226 @ 8 : 1e fa Star r0
0x1d4ea44d8228 @ 10 : 03 0a LdaSmi [10]
161 E> 0x1d4ea44d822a @ 12 : 5b fa 07 TestLessThan r0, [7]
0x1d4ea44d822d @ 15 : 86 23 JumpIfFalse [35] (0x1d4ea44d8250 @ 50)
150 E> 0x1d4ea44d822f @ 17 : 91 StackCheck
176 S> 0x1d4ea44d8230 @ 18 : 0a 01 0d LdaGlobal [1], [13]
0x1d4ea44d8233 @ 21 : 1e f9 Star r1
184 E> 0x1d4ea44d8235 @ 23 : 20 f9 02 0f LdaNamedProperty r1, [2], [15]
0x1d4ea44d8239 @ 27 : 1e fa Star r0
188 E> 0x1d4ea44d823b @ 29 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44d823e @ 32 : 1e f8 Star r2
184 E> 0x1d4ea44d8240 @ 34 : 4c fa f9 f8 0b CallProperty1 r0, r1, r2, [11]
168 S> 0x1d4ea44d8245 @ 39 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44d8248 @ 42 : 41 0a Inc [10]
168 E> 0x1d4ea44d824a @ 44 : 0c 00 08 StaGlobalSloppy [0], [8]
0x1d4ea44d824d @ 47 : 77 2a 00 JumpLoop [42], [0] (0x1d4ea44d8223 @ 5)
0x1d4ea44d8250 @ 50 : 04 LdaUndefined
192 S> 0x1d4ea44d8251 @ 51 : 95 Return
Constant pool (size = 3)
Handler Table (size = 16)
Bien sûr, d'autres compilateurs/interprètes JavaScript mai font autrement, mais cela est douteux.
En tant que dernier mot, pour ce qu'il vaut, je considère néanmoins comme une meilleure pratique d'utiliser le pré-incrément lorsque cela est possible: comme je change fréquemment de langue, je préfère utiliser la syntaxe avec le bon sémantique pour ce que je veulent, au lieu de compter sur l'intelligence du compilateur. Par exemple, les compilateurs C modernes ne feront aucune différence non plus. Mais en C++, cela peut avoir un impact significatif avec une surcharge operator++
.
Cela ressemble à une optimisation prématurée. Lorsque vous avez presque terminé votre application, vérifiez où se trouvent les goulots d'étranglement et optimisez-les au besoin. Mais si vous voulez un guide complet des performances de la boucle, vérifiez ceci:
http://blogs.Oracle.com/greimer/entry/best_way_to_code_a
Mais vous ne savez jamais quand cela deviendra obsolète en raison des améliorations du moteur JS et des variations entre les navigateurs. Le meilleur choix est de ne pas vous en préoccuper tant que ce n'est pas un problème. Rendez votre code clair à lire.
Edit: Selon ce gars le pré vs post est statistiquement insignifiant. (avec peut-être pire avant)
L'optimisation n'est pas l'incrément pré versus post. C'est l'utilisation d'opérateurs 'shift' et 'et' au niveau du bit plutôt que de diviser et de mod.
Il y a aussi l'optimisation de la réduction de la taille du javascript pour diminuer la taille totale (mais ce n'est pas une optimisation d'exécution).
Le test d'Anatoliy comprenait un post-incrément à l'intérieur de la fonction de test pré-incrément :(
Voici les résultats sans cet effet secondaire ...
function test_post() {
console.time('postIncrement');
var i = 1000000, x = 0;
do x++; while(i--);
console.timeEnd('postIncrement');
}
function test_pre() {
console.time('preIncrement');
var i = 1000000, x = 0;
do ++x; while(--i);
console.timeEnd('preIncrement');
}
test_post();
test_pre();
test_post();
test_pre();
test_post();
test_pre();
test_post();
test_pre();
Production
postIncrement: 3.21ms
preIncrement: 2.4ms
postIncrement: 3.03ms
preIncrement: 2.3ms
postIncrement: 2.53ms
preIncrement: 1.93ms
postIncrement: 2.54ms
preIncrement: 1.9ms
Voilà une grande différence.
Il s'agit probablement d'une programmation culte. Cela ne devrait pas faire de différence lorsque vous utilisez un compilateur/interprète décent pour les langues qui n'ont pas de surcharge d'opérateur arbitraire.
Cette optimisation était logique pour C++ où
T x = ...;
++x
pourrait modifier une valeur en place alors que
T x = ...;
x++
devrait créer une copie en faisant quelque chose sous le capot comme
T x = ...;
T copy;
(copy = T(x), ++x, copy)
ce qui pourrait être coûteux pour les grands types de structures ou pour les types qui effectuent beaucoup de calculs dans leur constructeur de copie.
Je viens de le tester dans Firebug et je n'ai trouvé aucune différence entre les post-incréments et les pré-incréments. Peut-être cette optimisation d'autres plates-formes? Voici mon code pour les tests Firebug:
function test_post() {
console.time('postIncrement');
var i = 1000000, x = 0;
do x++; while(i--);
console.timeEnd('postIncrement');
}
function test_pre() {
console.time('preIncrement');
var i = 1000000, x = 0;
do ++x; while(i--);
console.timeEnd('preIncrement');
}
test_post();
test_pre();
test_post();
test_pre();
test_post();
test_pre();
test_post();
test_pre();
La sortie est:
postIncrement: 140ms
preIncrement: 160ms
postIncrement: 136ms
preIncrement: 157ms
postIncrement: 148ms
preIncrement: 137ms
postIncrement: 136ms
preIncrement: 148ms