ChatGPT解决这个技术问题 Extra ChatGPT

反向循环真的更快吗?

我已经听过很多次了。向后计数时,JavaScript 循环真的更快吗?如果是这样,为什么?我已经看到一些测试套件示例显示反向循环更快,但我找不到任何解释为什么!

我假设这是因为循环不再需要在每次检查它是否完成时评估一个属性,它只是检查最终的数值。

IE

for (var i = count - 1; i >= 0; i--)
{
  // count is only evaluated once and then the comparison is always on 0.
}
呵呵。这将无限期地发生。试试我——
向后 for 循环更快,因为上限(呵呵,下限)循环控制变量不必定义或从对象中获取;它是一个常数零。
no real difference。原生循环结构总是非常快。不要担心他们的表现。
@Afshin:对于这样的问题,请向我们展示您所指的文章。
对于非常低端和电池供电的设备来说,差异主要是重要的。不同之处在于,使用 i-- 在循环结束时与 0 进行比较,而使用 i++ 与 number > 0 进行比较。我相信性能差异大约为 20 纳秒(类似于 cmp ax,0 与 cmp ax ,bx) - 如果你每秒循环数千次,为什么不为每个循环增加 20 纳秒 :)

J
Joel Christophel

并不是说 i--i++ 快。实际上,它们都同样快。

升序循环中需要时间的是评估每个 i 数组的大小。在这个循环中:

for(var i = array.length; i--;)

当您声明 i 时,您只计算一次 .length,而对于这个循环

for(var i = 1; i <= array.length; i++)

当您检查 i <= array.length 时,每次增加 i 时都会评估 .length

在大多数情况下,您甚至不应该担心这种优化。


是否值得为 array.length 引入一个变量并在 for 循环的头部使用它?
@ragatskynet:不,除非您正在设置基准并想提出一个观点。
@ragatskynet 这取决于:评估 .length 一定次数或声明和定义一个新变量会更快吗?在大多数情况下,这是过早的(并且是错误的)优化,除非您的 length 的评估成本非常高。
@Dr.Dredel:这不是比较——而是评估。 0 的评估速度比 array.length 快。嗯,据说。
值得一提的是,我们谈论的是解释型语言,例如 Ruby、Python。编译语言(例如 Java)在编译器级别上进行了优化,这将“平滑”这些差异,以至于 .length 是否在 for loop 的声明中并不重要。
B
Baldrickk

This guy 比较了许多浏览器中的许多 javascript 循环。他还有一个 test suite,因此您可以自己运行它们。

在所有情况下(除非我在阅读中错过了一个)最快的循环是:

var i = arr.length; //or 10
while(i--)
{
  //...
}

很好 :) 但是没有反向“for”循环测试......但是 peirix 或 searlea 提到的 for 循环应该与以“i--”作为条件的“while”循环几乎相同。这是测试过的最快的循环。
有趣,但我想知道预减量是否会更快。因为它不必存储 i 的中间值。
如果我没记错的话,我的硬件课程教授说,对 0 或不是 0 的测试是最快的“计算”。在 while(i--) 中,测试始终是 0 的测试。也许这就是为什么这是最快的?
@tvanfosson 如果您预先递减 --i 那么您必须在循环内使用 var current = arr[i-1]; 否则它将关闭一...
我听说 i-- 可以比 --i 快,因为在第二种情况下,处理器需要递减,然后针对新值进行测试(指令之间存在数据依赖性),而在第一种情况下,处理器可以测试现有值并在一段时间后减小该值。我不确定这是否适用于 JavaScript 或仅适用于非常低级的 C 代码。
P
Peter Mortensen

我试图用这个答案给出一个广泛的画面。

括号中的以下想法是我的信念,直到我最近测试了这个问题:

[[就像 C/C++ 这样的低级语言而言,代码经过编译,以便在变量为零(或非零)时处理器具有特殊的条件跳转命令。
另外,如果您关心这么多的优化,您可以使用 ++i 而不是 i++,因为 ++i 是单处理器命令,而 i++ 表示 j=i+1, i=j。]]

真正快速的循环可以通过展开它们来完成:

for(i=800000;i>0;--i)
    do_it(i);

它可能比

for(i=800000;i>0;i-=8)
{
    do_it(i); do_it(i-1); do_it(i-2); ... do_it(i-7);
}

但其原因可能相当复杂(顺便提一下,游戏中存在处理器命令预处理和缓存处理的问题)。

高级语言而言,如您所问的JavaScript,如果您依赖库、内置函数进行循环,则可以优化事物。让他们决定如何最好地完成。

因此,在 JavaScript 中,我建议使用类似

array.forEach(function(i) {
    do_it(i);
});

它也不易出错,并且浏览器有机会优化您的代码。

[备注:不仅是浏览器,您也有空间轻松优化,只需重新定义 forEach 函数(取决于浏览器),使其使用最新的最佳技巧! :) @AMK 说在特殊情况下更值得使用 array.poparray.shift。如果你这样做,把它放在窗帘后面。 最大的杀伤力是向 forEach 添加选项以选择循环算法。]

此外,对于低级语言,如果可能的话,最好的做法是使用一些智能库函数来进行复杂的循环操作。

这些库还可以将事物(多线程)放在您的背后,并且专业的程序员也可以使它们保持最新状态。

我做了更多的检查,结果发现在 C/C++ 中,即使是 5e9 = (50,000x100,000) 操作,如果针对像@alestanis 说的常量。 (JsPerf 结果有时不一致,但大体上说相同:你不能做出很大的改变。)
所以 --i 恰好是一个“豪华”的东西。它只会让你看起来像一个更好的程序员。 :)

另一方面,在这种 5e9 的情况下展开,它使我在 10 秒时从 12 秒降至 2.5 秒,在我前进 20 秒时降至 2.1 秒。它没有优化,优化已经把事情拖到了无法衡量的时间。 :) (展开可以按照我上面的方式或使用 i++ 完成,但这并不能在 JavaScript 中取得进展。)

总而言之:在工作面试中保持 i--/i++++i/i++ 差异,坚持使用 array.forEach 或其他复杂的库函数(如果可用)。 ;)


关键词是“可以”。您展开的循环也可能比原始循环慢。优化时,请始终进行测量,以便您准确了解更改产生的影响。
@jalf 确实如此,+1。不同的解环长度(>=1)在效率上是不同的。这就是为什么如果可能的话,把这项工作留给图书馆会更方便,更不用说浏览器在不同的架构上运行,所以如果他们决定如何做array.each(...)可能会更好。我认为他们不会尝试解开普通的 for 循环。
@BarnabasSzabolcs 这个问题专门针对 JavaScript,而不是 C 或其他语言。在 JS 中是有区别的,请看下面我的回答。虽然不适用于该问题,但+1好答案!
你去 - +1 给你一个 Great Answer 徽章。真的很棒的答案。 :)
APL/A++ 也不是一种语言。人们用这些来表达他们对语言细节不感兴趣,但对这些语言共享的东西感兴趣。
s
sainiuc

i--i++ 一样快

下面的代码和你的一样快,但是使用了一个额外的变量:

var up = Things.length;
for (var i = 0; i < up; i++) {
    Things[i]
};

建议不要每次都评估数组的大小。对于大型阵列,可以看到性能下降。


你在这里显然错了。现在循环控制需要额外的内部变量(i 和 up),并且取决于 JavaScript 引擎,这可能会限制优化代码的潜力。 JIT 会将简单循环转换为直接机器操作码,但如果循环有太多变量,那么 JIT 将无法优化。通常,它受到 JIT 使用的架构或 cpu 寄存器的限制。初始化一次并下降是最干净的解决方案,这就是为什么在所有地方都推荐它的原因。
附加变量将被通用解释器/编译器的优化器消除。关键是“与 0 比较” - 请参阅我的答案以获得进一步的解释。
最好将“向上”放在循环中:for (var i=0, up=Things.length; i<up; i++) {}
为什么内部变量会产生问题?这是错误的,因为循环控件没有“额外变量”,因为与 Things.length 完全相同的 up 不是通过引用传递(就像对象或数组那样),而是直接通过值传递。换句话说,它完全按照数字 10 或 100000 插入到循环控件中。您只是将它重命名为 OUTSIDE 的循环,所以没有任何区别。出于这个原因,答案本身是完全有效的。当看到 i <向上循环控制看到 i < 10 不是我 < (参考数字 10)。实际上 up 在其中存储了一个原语,不要忘记这一点。
d
dreamcrash

由于您对该主题感兴趣,请查看 Greg Reimer 关于 JavaScript 循环基准的博客文章What's the Fastest Way to Code a Loop in JavaScript?

我为 JavaScript 中不同的循环编码方式构建了一个循环基准测试套件。已经有一些这样的,但我没有发现任何承认本机数组和 HTML 集合之间的区别。

您还可以通过打开 https://blogs.oracle.com/greimer/resource/loop-test.html 进行循环性能测试(如果 JavaScript 在浏览器中被例如 NoScript 阻止,则不起作用)。

编辑:

Milan Adamovsky 创建的最新基准测试可以在运行时 here 中针对不同的浏览器执行。

对于 Mac OS X 10.6 上的 Firefox 17.0 中的测试,我得到了以下循环:

https://i.stack.imgur.com/yn6kQ.png


@dreamcash 在 Chrome 88.x 上,所有反向循环总是比所有正向循环快。 jsben.ch/eng8bmeasurethat.net/Benchmarks/ShowResult/162677jsbench.me/5ykkzsysa9。有时是反向的,有时是反向优化的,有时是反向的。
@johnywhy okey 我想念 - 理解 okey 所以它仍然有点不同。你会发布这些结果的答案吗?
P
Peter Mortensen

不是 --++,而是比较操作。使用 -- 您可以与 0 进行比较,而使用 ++ 您需要将其与长度进行比较。在处理器上,与零比较通常是可用的,而与有限整数比较需要减法。

a++ < length

实际上编译为

a++
test (a-length)

因此编译时处理器需要更长的时间。


B
BBog

我在 Sublime Text 2 中看到了同样的建议。

就像已经说过的那样,主要的改进不是在 for 循环的每次迭代中评估数组的长度。这是一种众所周知的优化技术,并且在数组是 HTML 文档的一部分时在 JavaScript 中特别有效(对所有 li 元素执行 for)。

例如,

for (var i = 0; i < document.getElementsByTagName('li').length; i++)

for (var i = 0, len = document.getElementsByTagName('li').length; i < len; i++)

从我的立场来看,您问题形式的主要改进是它没有声明额外的变量(在我的示例中为 len

但是,如果您问我,重点不在于 i++i-- 优化,而是不必在每次迭代时评估数组的长度(您可以在 jsperf 上查看基准测试)。


我不得不对这里的calculating这个词表示异议。请参阅我对 Pavel's answer 的评论。 ECMA 规范指出,当您引用它时,不会计算数组长度。
“评估”会是更好的选择吗?顺便说一句,有趣的事实,我不知道
附加变量将被通用解释器/编译器的优化器消除。这同样适用于 array.length 评估。关键是“与 0 比较” - 请参阅我的答案以获得进一步的解释。
@H.-DirkSchmitt 除了您的常识性答案,在 JavaScript 的历史上很长一段时间,编译器都没有优化查找链的性能成本。 AFAIK V8 是第一个尝试的。
@H.-DirkSchmitt 就像 kojiro 说的那样,这是众所周知且行之有效的把戏。即使它在现代浏览器中不再相关,但这仍然不会使其过时。此外,通过引入一个新的长度变量或使用OP问题中的技巧来做到这一点,它仍然是最好的方法,imo。这只是一件聪明的事情和良好的实践,我认为这与编译器经过优化以处理在 JS 中经常以不好的方式完成的事情没有任何关系
C
Community

简短的回答

对于普通代码,尤其是像 JavaScript 这样的高级语言,i++i-- 没有性能差异。

性能标准是在 for 循环和 compare 语句中的使用。

这适用于所有高级语言,并且大部分独立于 JavaScript 的使用。解释是最后一行的汇编代码。

详细解释

循环中可能会出现性能差异。背景是在 汇编代码 级别上,您可以看到 compare with 0 只是一个不需要额外寄存器的语句。

这种比较在循环的每一次通过时都会发出,并且可能会带来可衡量的性能改进。

for(var i = array.length; i--; )

将被评估为这样的伪代码:

 i=array.length
 :LOOP_START
 decrement i
 if [ i = 0 ] goto :LOOP_END
 ... BODY_CODE
 :LOOP_END

请注意,0 是文字,或者换句话说,是一个常量值。

for(var i = 0 ; i < array.length; i++ )

将被评估为这样的伪代码(假定正常的解释器优化):

 end=array.length
 i=0
 :LOOP_START
 if [ i < end ] goto :LOOP_END
 increment i
 ... BODY_CODE
 :LOOP_END

请注意,end 是一个需要 CPU 寄存器的变量。这可能会在代码中调用额外的寄存器交换,并且需要在 if 语句中使用更昂贵的比较语句

只是我的 5 美分

对于高级语言,便于维护的可读性作为较小的性能改进更为重要。

通常,从数组开始到结束的经典迭代会更好。

从数组结束到开始的更快迭代会导致可能不需要的反向序列。

后文

正如评论中所问的:--ii-- 的区别在于递减之前或之后对 i 的评估。

最好的解释是尝试一下;-) 这是一个 Bash 示例。

 % i=10; echo "$((--i)) --> $i"
 9 --> 9
 % i=10; echo "$((i--)) --> $i"
 10 --> 9

1+ 很好的解释。只是一个有点超出范围的问题,您能否解释一下 --ii-- 之间的区别?
P
Pavel P

我认为在 JavaScript 中说 i--i++ 快是没有道理的。

首先,它完全依赖于 JavaScript 引擎的实现。

其次,只要最简单的构造 JIT 并转换为本地指令,那么 i++i-- 将完全取决于执行它的 CPU。也就是说,在 ARM(移动电话)上,下降到 0 会更快,因为减量和与零比较是在单个指令中执行的。

可能,您认为一个比另一个更浪费,因为建议的方法是

for(var i = array.length; i--; )

但建议的方式不是因为一个比另一个快,而仅仅是因为如果你写

for(var i = 0; i < array.length; i++)

然后在每次迭代中必须评估 array.length(更智能的 JavaScript 引擎可能会发现循环不会改变数组的长度)。尽管它看起来像一个简单的语句,但它实际上是由 JavaScript 引擎在后台调用的某个函数。

另一个原因,为什么 i-- 可以被认为“更快”是因为 JavaScript 引擎只需要分配一个内部变量来控制循环(变量到 var i)。如果与 array.length 或其他变量进行比较,则必须有多个内部变量来控制循环,并且内部变量的数量是 JavaScript 引擎的有限资产。循环中使用的变量越少,JIT 优化的机会就越大。这就是为什么可以认为 i-- 更快...


关于如何评估 array.length 可能值得仔细措辞。当您引用它时,长度不是计算的。 (这只是 a property that gets set whenever an array index is created or changed)。如果有额外的开销,那是因为 JS 引擎没有优化那个名字的查找链。
好吧,不确定 Ecma 规范是怎么说的,但了解不同 JavaScript 引擎的一些内部结构并不简单getLength(){return m_length; },因为其中涉及到一些内务处理。但是,如果您尝试向后思考:编写需要实际计算长度的数组实现将是非常巧妙的:)
ECMA 规范要求已经计算了长度属性。每当添加或更改属性即数组索引时,必须立即更新 length
我想说的是,如果您尝试考虑一下,就很难违反规范。
x86 在这方面就像 ARM。 dec/jnzinc eax / cmp eax, edx / jne
A
A.M.K

由于其他答案似乎都没有回答您的具体问题(其中一半以上显示 C 示例并讨论低级语言,您的问题是针对 JavaScript)我决定自己编写。

所以,给你:

简单回答: i-- 通常更快,因为它不必每次运行时都与 0 进行比较,各种方法的测试结果如下:

测试结果: 正如 this jsPerf“证明”的那样,arr.pop() 实际上是迄今为止最快的循环。但是,正如您在问题中提出的那样,专注于 --ii--i++++i,以下是 jsPerf(它们来自多个 jsPerf,请参阅下面的来源)结果总结:

--ii-- 在 Firefox 中是相同的,而 i-- 在 Chrome 中更快。

在 Chrome 中,基本的 for 循环 (for (var i = 0; i < arr.length; i++)) 比 i----i 快,而在 Firefox 中则较慢。

在 Chrome 和 Firefox 中,缓存的 arr.length 显着快于 Chrome 大约 170,000 ops/sec。

没有显着差异,在大多数浏览器中,++ii++ 快,AFAIK,在任何浏览器中都不会反过来。

简短总结: arr.pop() 是迄今为止最快的循环;对于特别提到的循环,i-- 是最快的循环。

来源: http://jsperf.com/fastest-array-loops-in-javascript/15http://jsperf.com/ipp-vs-ppi-2

我希望这回答了你的问题。


看来您的 pop 测试似乎只是这么快,因为它在循环的大部分时间里将数组大小减小到 0 - 据我所知。但是为了确保事情是公平的,我设计了这个 jsperf 以在每个测试中以相同的方式创建数组 - 这似乎表明 .shift() 是我少数浏览器的赢家 - 虽然不是我所期望的 :) jsperf.com/compare-different-types-of-looping
因是唯一提到++i的人而被投票赞成:D
d
dreamcrash

这取决于数组在内存中的位置以及访问该数组时内存页面的命中率。

在某些情况下,由于命中率的增加,按列顺序访问数组成员比按行顺序更快。


如果只有OP问为什么以不同的顺序遍历相同的矩阵会花费不同的时间..
考虑到在操作系统中其内存管理是基于分页的,当一个进程需要一个不在缓存页面中的数据时,在操作系统中发生页面错误,它必须将目标页面带到CPU缓存并替换为另一个页面,因此,导致处理开销比目标页面位于 CPU 缓存中时花费更多时间。假设我们定义了一个大数组,其中的每一行都大于页面 OS 大小,并且我们按行顺序访问它,在这种情况下,页面错误率增加,结果比按列顺序访问该数组要慢。
页面错误与缓存未命中不同。如果您的内存被分页到磁盘,您只会出现页面错误。由于缓存局部性(在获取缓存行时使用缓存行的所有字节),而不是因为页面错误,因此按它们在内存中存储方式的顺序访问多维数组更快。 (除非您的工作集对于您正在使用的计算机来说太大了。)
P
Peter Mortensen

我最后一次担心它是在编写 6502 程序集(8 位,是的!)时。最大的收获是大多数算术运算(尤其是递减)更新了一组标志,其中一个是 Z,即“达到零”指示符。

因此,在循环结束时,您只需执行两条指令:DEC(递减)和 JNZ(如果不为零则跳转),无需比较!


在 JavaScript 的情况下,它显然不适用(因为它在没有此类操作码的 CPU 上运行)。 i--i++ 背后的真正原因很可能是前者不会在循环范围内引入额外的控制变量。请看下面我的回答...
对,真的不适用;但这是一种非常常见的 C 风格,对于我们这些习惯了它的人来说,它确实看起来更干净。 :-)
x86 和 ARM 在这方面都像 6502。 dec/jnz 而不是 inc/cmp/jne 用于 x86 情况。您不会看到空循环运行得更快(两者都会使分支吞吐量饱和),但倒计时会稍微减少循环开销。当前的英特尔硬件预取器也不受按降序排列的内存访问模式的困扰。我认为较旧的 CPU 可以跟踪 4 个反向流和 6 个或 10 个正向流,IIRC。
p
peirix

你现在这样做的方式并不快(除了它是一个无限循环之外,我猜你打算这样做 i--.

如果您想让它更快,请执行以下操作:

for (i = 10; i--;) {
    //super fast loop
}

当然你不会在这么小的循环上注意到它。它更快的原因是因为你在检查它是否为“真”时递减 i (当它达到 0 时它评估为“假”)


您是否缺少分号? (i = 10; i--;)
是的,是的,我改正了错误。如果我们要挑剔的话,我会指出你在你的 i- 之后忘记了你的分号!呵呵。
为什么会更快?更短的源并不意味着它会更快。你量过吗?
是的,我已经测量过了,我并不是说更短的源让它更快,而是更少的操作让它更快。
这是一个展示差异的基准 - jsbench.me/1cknepoalw/1
s
searlea

它可以通过 JavaScript(和所有语言)最终变成操作码在 CPU 上运行来解释。 CPU 总是有一条指令用于与零进行比较,这非常快。

顺便说一句,如果您可以保证 count 始终是 >= 0,您可以简化为:

for (var i = count; i--;)
{
  // whatever
}

更短的源代码并不一定意味着它会更快。你量过吗?
哎呀,我错过了这个。钉在头上有小伙子。
我想看看与 0 比较不同的程序集源。已经有几年了,但有一次我做了大量的汇编编码,我一辈子都想不出一种方法来以一种不能同样快的方式对 0 进行比较/测试对于任何其他整数。然而,你说的确实是真的。很沮丧,我不知道为什么!
@Brian Knoblauch:如果您使用诸如“dec eax”(x86 代码)之类的指令,那么该指令会自动设置 Z(零)标志,您可以立即对其进行测试,而无需在其间使用另一条比较指令。
不过,在解释 Javascript 时,我怀疑操作码是否会成为瓶颈。更可能的是,更少的标记意味着解释器可以更快地处理源代码。
P
Peter Mortensen

for(var i = array.length; i--; ) 并没有快多少。但是,当您将 array.length 替换为 super_puper_function() 时,可能会明显更快(因为它在每次迭代中都会被调用)。这就是区别。

如果你打算在 2014 年改变它,你不需要考虑优化。如果您要使用“搜索和替换”进行更改,则无需考虑优化。如果你没有时间,你不需要考虑优化。但现在,你有时间考虑一下。

PS:i-- 并不比 i++ 快。


C
Community

简而言之:在 JavaScript 中执行此操作绝对没有区别。

首先,你可以自己测试一下:

jsperf - 是用于 JavaScript 中各种性能测试的优秀平台。

http://jsperf.com/inc-vs-dec-2

您不仅可以测试和运行任何 JavaScript 库中的任何脚本,还可以访问所有以前编写的脚本,以及查看不同平台上不同浏览器中执行时间之间的差异的能力。

所以就你所见,任何环境下的性能都没有区别。

如果您想提高脚本的性能,您可以尝试执行以下操作:

有一个 var a = array.length;语句,这样您就不会在循环中每次都计算其值 Do loop unrolling http://en.wikipedia.org/wiki/Loop_unwinding

但你必须明白,你能获得的进步是如此微不足道,以至于你甚至不应该关心它。

我自己的看法为什么会出现这样的误解(Dec vs Inc)

很久很久以前,有一个通用的机器指令 DSZ(减零并跳过零)。用汇编语言编程的人使用这条指令来实现循环以保存寄存器。现在这个古老的事实已经过时了,我很确定你不会使用这种伪改进在任何语言中获得任何性能改进。

我认为在我们这个时代传播这种知识的唯一方法是阅读他人的个人代码。看到这样的结构并询问为什么要实施它,这里的答案是:“它提高了性能,因为它与零相比”。您对同事的更高知识感到困惑,并认为使用它来变得更聪明:-)


有趣,但是对于您在 win7 上在 firefox 16.0.2 下运行的测试,递减循环慢了 29%... 编辑:忽略这一点。重复测试被证明是不确定的,在我的后续运行的测试结果中存在令人惊讶的噪音。不太清楚为什么。
是的,我试图通过关闭其他所有内容并运行测试来解决这一问题。仍然得到了不稳定的结果。很奇怪。
我认为你错过了为什么在 JavaScript 中归零被认为更好的真正意义。这主要是因为这种方式只有一个变量控制循环执行,例如这种方式优化器/JITer 有更大的改进空间。使用 array.length 不一定会导致性能损失,仅仅是因为 JS 虚拟机足够聪明,可以判断数组是否没有被循环体修改。请看下面我的回答。
吹毛求疵:这个古老的事实(汇编语言优化)并没有过时,只是晦涩难懂。因为你不需要知道,除非你真的知道。 :-)
S
Spyryto

我做了一个comparison on jsbench

正如 alestani 指出的,在升序循环中需要时间的一件事是,对于每次迭代,评估数组的大小。在这个循环中:

for ( var i = 1; i <= array.length; i++ )

每次增加 i 时都会评估 .length。在这个:

for ( var i = 1, l = array.length; i <= l; i++ )

当您声明 i 时,您只计算一次 .length。在这个:

for ( var i = array.length; i--; )

比较是隐式的,它发生在递减 i 之前,并且代码非常可读。但是,可以产生巨大影响的是您放入循环中的内容。

循环调用函数(在别处定义):

for (i = values.length; i-- ;) {
  add( values[i] );
}

使用内联代码循环:

var sum = 0;
for ( i = values.length; i-- ;) {
  sum += values[i];
}

如果你可以内联你的代码,而不是调用一个函数,而不牺牲易读性,你可以让循环快一个数量级!

注意:由于浏览器是becoming good at inlining简单的功能,这实际上取决于您的代码的复杂程度。所以,优化之前的配置文件,因为

瓶颈可能在其他地方(ajax,reflow,...)您可能会选择更好的算法您可能会选择更好的数据结构

但要记住:

代码是为人们阅读而编写的,只是为了让机器执行。


+1 对于这个答案和基准。我添加了 forEach 测试并将基准重新设计为可在浏览器和 Node.js 中运行的独立文件。 jsfiddle.net/hta69may/2。对于节点“反向循环,隐式比较,内联代码”是最快的。但是在 FF 50 中的测试显示了奇怪的结果:不仅时间几乎减少了 10 倍(!),而且两个“forEach”测试都与“反向循环”一样快。也许 Node 的人应该使用 Mozilla 的 JS 引擎而不是 V8? :)
P
Peter Mortensen

这不取决于 --++ 符号,但取决于您在循环中应用的条件。

例如:如果变量具有静态值,则循环比每次循环检查条件(如数组长度或其他条件)时更快。

但是不要担心这种优化,因为这次它的效果是以纳秒为单位来衡量的。


多个纳秒循环可以变成几秒钟......当你有时间优化它从来都不是一个坏主意
S
Salman A

++-- 无关紧要,因为 JavaScript 是一种解释型语言,而不是一种编译型语言。每条指令都翻译成不止一种机器语言,你不应该关心血淋淋的细节。

那些谈论使用 --(或 ++)来有效使用汇编指令的人是错误的。这些指令适用于整数运算并且有no integers in JavaScript, just numbers

您应该编写可读的代码。


A
Ant

曾经有人说 --i 更快(在 C++ 中),因为只有一个结果,即递减值。 i-- 需要将递减的值存储回 i 并保留原始值作为结果 (j = i--;)。在大多数编译器中,这使用了两个寄存器而不是一个,这可能导致另一个变量必须写入内存而不是作为寄存器变量保留。

我同意其他人所说的这些天没有什么不同。


基准测试无处不在:jsben.ch:--i 更快,jsben.ch/RpG0K。 jsbench.me:i-- 更快,jsbench.me/i2kkzuk4kl/1。 measurethat.net:--i 更快,measurethat.net/Benchmarks/ShowResult/162675
K
Kunwar Siddharth Singh

有时,对我们编写代码的方式进行一些非常小的更改可能会对我们的代码实际运行的速度产生很大的影响。一个小的代码更改可以对执行时间产生很大影响的一个领域是我们有一个处理数组的 for 循环。如果数组是网页上的元素(例如单选按钮),则更改的效果最大,但即使数组位于 Javascript 代码内部,仍然值得应用此更改。

编码 for 循环以处理数组 lis 的传统方法如下:

for (var i = 0; i < myArray.length; i++) {...

这样做的问题是使用 myArray.length 评估数组的长度需要时间,而我们编写循环的方式意味着每次都必须在循环周围执行此评估。如果数组包含 1000 个元素,则数组的长度将被计算 1001 次。如果我们正在查看单选按钮并且有 myForm.myButtons.length 则评估将花费更长的时间,因为必须首先定位指定表单中的适当按钮组,然后才能在每次循环中评估长度。

显然,我们不希望在处理数组时改变数组的长度,所以所有这些长度的重新计算只是不必要地增加了处理时间。 (当然,如果你在循环中有添加或删除数组条目的代码,那么数组大小可以在迭代之间改变,所以我们不能改变测试它的代码)

对于大小固定的循环,我们可以做些什么来纠正这个问题,即在循环开始时评估一次长度并将其保存在变量中。然后我们可以测试变量来决定何时终止循环。这比每次评估数组长度要快得多,特别是当数组包含多个条目或者是网页的一部分时。

执行此操作的代码是:

for (var i = 0, var j = myArray.length; i < j; i++) {...

因此,现在我们只评估一次数组的大小,并针对每次在循环中保存该值的变量测试我们的循环计数器。这个额外变量的访问速度比评估数组的大小要快得多,因此我们的代码将比以前运行得更快。我们的脚本中只有一个额外的变量。

通常,只要数组中的所有条目都得到处理,我们处理数组的顺序并不重要。在这种情况下,我们可以通过取消我们刚刚添加的额外变量并以相反的顺序处理数组来使我们的代码稍微快一些。

以最有效的方式处理我们的数组的最终代码是:

for (var i = myArray.length-1; i > -1; i--) {...

这段代码仍然只在开始时计算一次数组的大小,但是我们没有将循环计数器与变量进行比较,而是将其与常量进行比较。由于访问常量比访问变量更有效,并且由于我们的赋值语句比以前少了,所以我们的第三版代码现在比第二版更高效,并且比第一版更高效。


A
Artelius

在许多情况下,这与处理器可以比其他比较更快地比较零的事实基本上无关。

这是因为 only a few Javascript engines(JIT 列表中的那些)实际上会生成机器语言代码。

大多数 Javascript 引擎都会构建源代码的内部表示,然后它们会对其进行解释(要了解这是什么样的,请查看 this page on Firefox's SpiderMonkey 的底部附近)。一般来说,如果一段代码实际上做了同样的事情,但会导致更简单的内部表示,它会运行得更快。

请记住,对于简单的任务,例如从变量中添加/减去一个,或者将变量与某物进行比较,解释器从一个内部“指令”移动到下一个内部“指令”的开销是相当高的,因此更少的“指令”是由JS引擎内部使用,效果更好。


t
the swine

好吧,我不了解 JavaScript,它实际上应该只是重新评估数组长度的问题,也许与关联数组有关(如果你只递减,不太可能需要分配新条目 - 如果数组是密集的,也就是说。有人可能会为此优化)。

在低级汇编中,有一个循环指令,称为 DJNZ(如果非零则递减和跳转)。因此,递减和跳转都在一条指令中,使其可能比 INC 和 JL / JB 稍微快一点(递增,如果小于则跳转/如果低于则跳转)。此外,与零比较比与另一个数字比较更简单。但所有这些实际上都是微不足道的,并且还取决于目标架构(例如在智能手机中的 Arm 上可能会有所不同)。

我没想到这种低级差异会对解释语言产生如此大的影响,我只是没有在回复中看到 DJNZ,所以我想我会分享一个有趣的想法。


作为记录,DJNZ 是 8051 (z80) ISA 中的指令。 x86 有 dec/jnz 而不是 inc/cmp/jne,显然 arm 有类似的东西。我很确定这不是 Javascript 差异的原因;那是在循环条件中有更多到 eval 。
R
Ravi_Parmar

用非常简单的话

“i-- 和 i++。实际上,它们都需要相同的时间”。

但是在这种情况下,当您进行增量操作时..处理器在每次变量增加 1 时评估 .length 并且在减少的情况下..特别是在这种情况下,它只会评估 .length 一次,直到我们得到 0。


D
Dmitry

首先,i++i-- 在任何编程语言(包括 JavaScript)上花费的时间完全相同。

下面的代码需要很多不同的时间。

快速地:

for (var i = 0, len = Things.length - 1; i <= len; i++) { Things[i] };

减缓:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

因此以下代码也需要不同的时间。

快速地:

for (var i = Things.length - 1; i >= 0; i--) { Things[i] };

减缓:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

由于编译器的优化,PS Slow 仅对少数语言(JavaScript 引擎)很慢。最好的方法是使用 '<' 代替 '<='(或 '=')和 '--i' 代替 'i--'。


P
Peter Mortensen

i-- 或 i++ 消耗的时间并不多。如果您深入 CPU 架构,++-- 更快,因为 -- 操作将执行 2 的补码,但它发生在硬件内部,因此这将使其速度更快,并且两者之间没有重大区别++-- 这些操作也被认为是 CPU 消耗的最少时间。

for 循环运行如下:

在开始时初始化变量一次。

检查循环的第二个操作数中的约束,<、>、<= 等。

然后应用循环。

增加循环并再次循环再次抛出这些过程。

所以,

for (var i = Things.length - 1; i >= 0; i--) {
    Things[i]
}; 

将在开始时仅计算一次数组长度,这不是很多时间,但是

for(var i = array.length; i--; ) 

会计算每个循环的长度,所以会消耗很多时间。


var i = Things.length - 1; i >= 0; i-- 也会计算长度 1 次。
我不确定“-- 操作将做 2 的补码”是什么意思,但我认为这意味着它会否定某些东西。不,它不会否定任何架构上的任何内容。减 1 就像加 1 一样简单,你只需创建一个借用而不是进位的电路。
G
Greg Hewgill

回答此类问题的最佳方法是实际尝试。设置一个计算一百万次迭代或其他的循环,并以两种方式进行。对两个循环进行计时,并比较结果。

答案可能取决于您使用的浏览器。有些会产生与其他人不同的结果。


那仍然无法回答他关于为什么它更快的问题。他只是得到一个基准,不知道为什么会这样……
确实如此。但是,如果不知道每个浏览器中每个 Javascript 引擎的确切实现,几乎不可能回答“为什么”部分。这里的很多答案都围绕着一些轶事建议,例如“使用前减量而不是后减量”(一种 C++ 优化技巧)和“与零比较”,这在本机代码编译语言中可能是正确的,但 Javascript 与中央处理器。
-1 我完全不同意最好的方法是尝试一下。几个例子并不能取代集体知识,这就是像这样的论坛的全部意义所在。
R
Robert

喜欢它,很多标记但没有答案:D

简单地说,与零的比较总是最快的比较

所以 (a==0) 实际上返回 True 比 (a==5) 更快

它很小且微不足道,并且在一个集合中有 1 亿行,它是可测量的。

即在循环中,您可能会说 i <= array.length 并递增 i

在向下循环中,您可能会说 where i >= 0 而是递减 i。

比较比较快。不是循环的“方向”。


上述问题没有答案,因为 Javascript 引擎各不相同,答案完全取决于您在哪个浏览器上测量它。
不,从根本上来说,与零的比较是最快的。尽管您的陈述也是正确的,但与零进行比较的黄金法则是绝对的。
只有当编译器选择进行优化时,这才是正确的,这当然不能保证。编译器可能会为 (a==0) 和 (a==5) 生成完全相同的代码,但常量的值除外。如果比较的双方都是变量(从 CPU 的角度来看),CPU 与 0 的比较不会比与任何其他值的比较快。通常只有本机代码编译器才有机会在此级别进行优化。
m
mrbinky3000

帮助他人避免头痛 --- 投票!

此页面上最受欢迎的答案不适用于 Firefox 14,并且未通过 jsLinter。 “while”循环需要比较运算符,而不是赋值。它确实适用于 chrome、safari 甚至 ie。但死在Firefox中。

这已破了!

var i = arr.length; //or 10
while(i--)
{
  //...
}

这会起作用的! (在 Firefox 上工作,通过 jsLinter)

var i = arr.length; //or 10
while(i>-1)
{
  //...
  i = i - 1;
}

我刚刚在 Firefox 14 上尝试过,效果很好。我在生产库中看到了使用 while (i--) 并且可以在许多浏览器上运行的示例。你的测试可能有什么奇怪的地方吗?您使用的是 Firefox 14 的测试版吗?
R
Rorchackh

这只是一个猜测,但可能是因为处理器更容易将某些内容与 0 ( i >= 0 ) 进行比较,而不是与另一个值 ( i < Things.length) 进行比较。


+1 其他人没有投票否决。尽管 .length 的重复计算比实际的增量/减量更成问题,但检查循环结束可能很重要。我的 C 编译器对循环发出一些警告,例如:“remark #1544-D: (ULP 13.1) Detected loop count up. Recommend loops count down as detection zeros are more more”