42

当我偶然发现这个名为 JSpeed - Javascript 优化的项目时,我正在浏览 Google 代码。

我注意到其中一项优化是更改i++++iin for 循环语句。

优化前

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

我知道前后增量的作用,但是知道这如何加快代码速度吗?

4

9 回答 9

63

这就是我阅读的内容,可以回答您的问题:“preincrement ( ++i) 将 的值加一i,然后返回i;相反,i++返回i然后将其加一,这在理论上会导致创建一个临时变量来存储 的值i在应用增量操作之前”。

于 2009-10-10T06:34:39.503 回答
58

这是一个虚假的优化。据我了解,您正在保存 1 个操作码。如果你想用这种技术优化你的代码,那么你就走错了路。此外,无论如何,大多数编译器/解释器都会为您优化这一点(参考 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 容器)上完成的,实际上并没有太多优化。

于 2009-10-10T09:01:51.973 回答
10

理论上,使用后自增运算符可能会产生一个临时的。实际上,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();

在这种情况下,NodeJS 中的 V8 编译器生成完全相同的字节码(尤其是在操作码 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 编译器也不会产生任何影响。但在 C++ 中,这会对重载的operator++.

于 2018-01-07T11:13:07.053 回答
3

Anatoliy 的测试包括在预增量测试函数中的后增量:(

这是没有这种副作用的结果......

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

这是一个很大的区别。

于 2014-07-31T10:13:11.647 回答
3

听起来像过早的优化。当您几乎完成您的应用程序时,请检查瓶颈在哪里并根据需要进行优化。但是,如果您想要一个完整的循环性能指南,请查看:

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

但是你永远不知道什么时候会因为 JS 引擎的改进和浏览器之间的变化而过时。最好的选择是在出现问题之前不要担心它。使您的代码清晰易读。

编辑:根据这个家伙的说法,前后对比在统计上是微不足道的。(pre 可能更糟)

于 2009-10-10T03:51:57.607 回答
2

优化不是前后增量。这是使用按位“移位”和“与”运算符而不是除法和取模。

还有缩小 javascript 以减小总大小的优化(但这不是运行时优化)。

于 2009-10-10T03:50:05.640 回答
1

这可能是货物狂热的编程。当您为没有任意运算符重载的语言使用体面的编译器/解释器时,它不应该有所作为。

这种优化对 C++ 有意义

T x = ...;
++x

可以修改一个值,而

T x = ...;
x++

必须通过做一些底层的事情来创建一个副本,比如

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

对于大型结构类型或在其“复制构造函数”中进行大量计算的类型,这可能会很昂贵。

于 2012-07-30T20:10:35.007 回答
0

刚刚在 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
于 2009-10-10T07:10:53.773 回答
0

使用后增量会导致堆栈溢出。为什么?start 和 end 将始终返回相同的值,而无需先递增

function reverseString(string = [],start = 0,end = string.length - 1) {  
  if(start >= end) return
  let temp = string[start]
  string[start] = string[end]
  string[end] = temp
  //dont't do this
  //reverseString(string,start++,end--)
  reverseString(string,++start,--end)
  return array
}

let array = ["H","a","n","n","a","h"]
console.log(reverseString(array))

于 2021-04-04T10:56:49.247 回答