为啥在嵌套函数之外声明一个计数器变量会使循环慢 5 倍?

Posted

技术标签:

【中文标题】为啥在嵌套函数之外声明一个计数器变量会使循环慢 5 倍?【英文标题】:Why does declaring a counter variable outside of a nested function make a loop 5x slower?为什么在嵌套函数之外声明一个计数器变量会使循环慢 5 倍? 【发布时间】:2018-03-14 09:00:38 【问题描述】:

我正在寻找一些我正在重新访问的 javascript 遗留代码的微优化,并注意到在最频繁调用的 for 循环中,计数器在使用它们的函数之外在全局范围内声明一次。我很好奇这是否真的是一种优化,因此我在 JavaScript 中创建了以下测试用例:

var tmp = 0;

function test()

    let j = 0;

    function letItBe()

        for(j = 0; j < 1000; j++)
            tmp = Math.pow(j, 2);
        
    

    function letItNotBe()
        for(let l = 0; l < 1000; l++)
            tmp = Math.pow(l, 2);
        
    

    console.time("let it be");
    for(var i =0; i < 10000; i++)

        letItBe();
    
    console.timeEnd("let it be");


    console.time("let it not be");
    for(var i =0; i < 10000; i++)

        letItNotBe();
    
    console.timeEnd("let it not be");


test();

在 Chrome、Firefox 和 NodeJS 中,letItNotBe() 的运行速度明显快于 letItBe()

铬:

NodeJS:

var 改变 let 没有任何区别。

最初我的逻辑是,每次调用函数时声明一个新的计数器变量确实比最初声明一个变量然后简单地重置为 0 慢。然而,事实证明这是完全相反的和不同的在执行时间上是相当实质性的。

我的简单解释是,当计数器变量在使用它的函数之外声明时,JS 转译器需要以某种方式引用这个变量。而且由于它在父范围内,因此在递增时需要更多的执行来引用它。但这只是盲目的猜测。

任何人都可以给出任何有意义的解释为什么会发生这种情况,因为除了我已经拥有的测试之外,我需要重构代码并给出有意义的解释 mysefl :) 谢谢。

【问题讨论】:

可能是因为它不必在 for 循环之外的范围内查找变量。 @GeorgeJempty 是吗?这也是我的猜测......但它是吗? @PeterCordes 颠倒顺序没有任何改变,结果相同。进行更多的迭代也不会改变任何事情。调整 CPU 时钟速度,没有任何改变。连续运行两个函数 2 次,没有任何变化。 :) @Liam 是的,它可以计算物理和粒子之间的碰撞,它是 O(n^2) 并且每个 FPS 都会调用它 @Liam 知道原因可能很重要,这样您就可以避免进行缓慢的迭代。此外,我们不应从字面上坚持代码示例,而应始终将这些问题视为通用问题,其中问题中的代码只是该通用问题的单个实现。这样,cmets 和可能的答案对未来的读者也更有用。 【参考方案1】:

我读过一本书High Performance JavaScript,作者在第 2 章“数据访问”-“管理范围”-“标识符解析性能”部分对此进行了解释。

标识符解析不是免费的,因为实际上没有计算机操作 确实没有某种性能开销。越深入 执行上下文的作用域链存在标识符,越慢 它是用于读取和写入的访问。因此,当地 在函数内部访问变量总是最快的, 而全局变量通常是最慢的(优化 JavaScript 引擎能够在某些情况下对此进行调整)。

...

所有浏览器的总体趋势是越深入 范围链标识符存在,读取它的速度越慢,或者 写给。

...

鉴于此信息,建议在任何时候都使用局部变量 可以在不优化的情况下提高浏览器的性能 JavaScript 引擎。一个好的经验法则是始终存储 如果多次使用局部变量中的超出范围的值 在函数中。

在您的情况下,letItBeletItNotBe 以相同的方式工作,使用相同的范围外 tmp 变量,它们都是闭包。唯一的区别是 @987654326 的计数器变量@循环:

变量j是为函数test()定义的,它对于函数letItBe()是“超出范围”的,所以执行letItBe()会导致引擎在标识符解析上做更多的工作 变量lfor循环范围内定义(见let keyword in the for loop),所以解析更快

【讨论】:

这表明 letItBe() 中的局部变量会很快,无论它是否在 for 循环本身中声明。你检查过吗?即问题标题没有指出它不仅在for 循环之外,而且在for 循环所在的嵌套函数之外? @PeterCordes 你是对的,只是当我写标题时,我发现很难想出一个简短的描述性标题,准确地反映这个案例。是的,在 for 循环外的函数内声明变量与在 for 循环内声明它一样快。 我实际上会接受这个答案,因为它基本上引用了可信赖的来源(一本 JS 书),它证实了我和大多数人对为什么会发生这种情况的猜测。 @Lys:标题很难。我通常最终会让它们变长,但这比通用或不准确的更好,IMO。当问题标题说明真正发生了什么时,人们更容易找到有趣/相关的问题。 (有时为未来的读者写一个好的标题需要知道答案,但这很好,以及编辑的目的是什么:)顺便说一句,这个问题在我重新命名后的 10 分钟内从 2 到 4 个赞成票,所以我认为描述嵌套更有趣/更明智。 @PeterCordes 感谢您的编辑和您的时间 :) 我通常会尽力做到最好,但正如您提到的那样,有时这很难并且只是考虑编辑它,当我看到您做到了给我!

以上是关于为啥在嵌套函数之外声明一个计数器变量会使循环慢 5 倍?的主要内容,如果未能解决你的问题,请参考以下文章

js代码嵌套问题,为啥外层定义的变量内层不能使用

在循环中声明的变量是不是会使空间复杂度 O(N)?

(JavaScript) 为啥 while 循环中的“if”中的 continue 语句会使浏览器崩溃?

为啥声明表变量与临时表相比非常慢?

itertools.product 比嵌套 for 循环慢

为啥在 Chrome 上的 for 循环中使用 let 这么慢?