постинкремент против предварительного инкремента - Оптимизация Javascript - PullRequest
32 голосов
/ 10 октября 2009

Я просматривал Google Code, когда случайно наткнулся на этот проект под названием JSpeed ​​- оптимизация для Javascript.

Я заметил, что одной из оптимизаций было изменение i++ на ++i для операторов цикла.

До оптимизации

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++) {}

После оптимизации

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){}

Я знаю, что делают до и после приращения, но есть идеи, как это ускоряет код?

Ответы [ 8 ]

56 голосов
/ 10 октября 2009

Это ложная оптимизация. Насколько я понимаю, вы сохраняете 1 код операции. Если вы хотите оптимизировать свой код с помощью этой техники, то вы пошли по неверному пути. Кроме того, большинство компиляторов / интерпретаторов все равно оптимизируют это для вас ( reference 1 ). Короче я бы не переживал. Но , если вы действительно волнуетесь, вы должны использовать i+=1.

Вот быстрый и грязный тест, который я только что сделал

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

Необработанные результаты

Post    Pre     +=
1071    1073    1060
1065    1048    1051
1070    1065    1060
1090    1070    1060
1070    1063    1068
1066    1060    1064
1053    1063    1054

Удалены самые низкие и самые высокие

Post    Pre     +=
1071    ----    1060
1065    ----    ----
1070    1065    1060
----    1070    1060
1070    1063    ----
1066    1060    1064
----    1063    1054

Среды

1068.4  1064.2  1059.6

Обратите внимание, что это больше миллиона итераций , а результаты в среднем находятся в пределах 9 миллисекунд. На самом деле оптимизация невелика, учитывая, что большая часть итеративной обработки в JavaScript выполняется для гораздо меньших наборов (например, для DOM-контейнеров).

46 голосов
/ 10 октября 2009

Это то, что я прочитал и могу ответить на ваш вопрос: «preincrement (++i) добавляет единицу к значению i, затем возвращает i; напротив, i++ возвращает i, затем добавляет один к этому, что в теории приводит к созданию временной переменной, хранящей значение i до применения операции приращения ".

7 голосов
/ 07 января 2018

Теоретически, использование оператора пост-инкремента может создать временный. На практике компиляторы JavaScript достаточно умны, чтобы этого избежать, особенно в таком тривиальном случае.

Например, давайте рассмотрим этот пример кода:

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

В этом случае компилятор V8 в NodeJS выдает точно тот же самый байт-код (см., Например, опкоды 39-44 для приращения):

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)

Конечно, другие компиляторы / интерпретаторы JavaScript могут поступить иначе, но это сомнительно.

В качестве последнего слова, для чего бы это ни стоило, я, тем не менее, считаю, что лучше всего использовать предварительное увеличение, когда это возможно: поскольку я часто переключаю языки, я предпочитаю использовать синтаксис с правильным семантическим для то, что я хочу, вместо того, чтобы полагаться на смекалку компилятора. Например, современные компиляторы Си также не будут иметь никакого значения. Но в C ++ это может оказать значительное влияние с перегруженным operator++.

3 голосов
/ 10 октября 2009

Звучит как преждевременная оптимизация. Когда вы почти закончили свое приложение, проверьте, где находятся узкие места, и оптимизируйте их по мере необходимости. Но если вам нужно подробное руководство по повышению производительности, проверьте это:

http://blogs.oracle.com/greimer/entry/best_way_to_code_a

Но вы никогда не знаете, когда это устареет из-за улучшений движка JS и различий между браузерами. Лучший выбор - не беспокоиться об этом, пока это не станет проблемой. Сделайте ваш код понятным для чтения.

Редактировать: Согласно этот парень до и после является статистически незначимым. (с предварительным, возможно, хуже)

2 голосов
/ 10 октября 2009

Оптимизация не является пре-против пост-приращением. Это использование побитовых операторов 'shift' и 'and', а не деления и мода.

Существует также оптимизация минимизации JavaScript для уменьшения общего размера (но это не оптимизация во время выполнения).

1 голос
/ 31 июля 2014

Тест Анатолия включал постинкремент внутри функции теста перед инкрементом: (

Вот результаты без этого побочного эффекта ...

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

выход

postIncrement: 3.21ms
preIncrement: 2.4ms
postIncrement: 3.03ms
preIncrement: 2.3ms
postIncrement: 2.53ms
preIncrement: 1.93ms
postIncrement: 2.54ms
preIncrement: 1.9ms

Это большая разница.

1 голос
/ 31 июля 2012

Это, вероятно, культовое программирование. Это не должно иметь значения, когда вы используете достойные компиляторы / интерпретаторы для языков, в которых нет произвольной перегрузки операторов.

Эта оптимизация имела смысл для C ++, где

T x = ...;
++x

может изменить значение на месте, тогда как

T x = ...;
x++

пришлось бы создать копию, сделав что-то скрытое, как

T x = ...;
T copy;
(copy = T(x), ++x, copy)

, что может быть дорого для больших структурных типов или для типов, которые много вычисляют в своем конструкторе копирования.

0 голосов
/ 10 октября 2009

Только что проверил его в firebug и не обнаружил разницы между пост- и прекрементами. Может быть, это оптимизация других платформ? Вот мой код для тестирования 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();

Вывод:

postIncrement: 140ms
preIncrement: 160ms
postIncrement: 136ms
preIncrement: 157ms
postIncrement: 148ms
preIncrement: 137ms
postIncrement: 136ms
preIncrement: 148ms
...