web-dev-qa-db-fra.com

post-incrément vs pré-incrément - Optimisation Javascript

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?

36
mauris

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 " .

53
KooiInc

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).

58
Justin Johnson

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++.

7
Sylvain Leroux

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)

3
Glenn

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).

2
Taylor Leese

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.

1
pierre v

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.

1
Mike Samuel

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
0
Anatoliy