gpt4 book ai didi

javascript - 为什么使用 window.variable 访问变量速度较慢?

转载 作者:IT王子 更新时间:2023-10-29 03:12:43 26 4
gpt4 key购买 nike

JS 性能提示的多个来源鼓励开发人员减少“作用域链查找”。例如,当您访问全局变量时,IIFE 被吹捧为具有“减少作用域链查找”的额外好处。这听起来很合乎逻辑,甚至可能被认为是理所当然的,所以我没有质疑其中的智慧。和许多其他人一样,我一直很高兴地使用 IIFE,认为除了避免全局命名空间污染之外,还会有比任何全局代码更高的性能提升。

我们今天的期望:

(function($, window, undefined) {
// apparently, variable access here is faster than outside the IIFE
})(jQuery, window);

将其简化/扩展到一般情况,人们会期望:

var x = 0;
(function(window) {
// accessing window.x here should be faster
})(window);

根据我对JS的理解,x = 1;window.x = 1;在全局范围内没有区别。因此,期望它们具有同等性能是合乎逻辑的,对吗? 错误。我进行了一些测试,发现访问时间存在显着差异。

好吧,也许如果我将 window.x = 1; 放在 IIFE 中,它应该运行得更快(即使只是一点点),对吧? 又错了。

好吧,也许是 Firefox;让我们试试 Chrome(V8 是 JS 速度的基准,是吗?)它应该在简单的事情上击败 Firefox,比如直接访问全局变量,对吧? 又错了

所以我着手找出在这两种浏览器中哪种访问方法最快。假设我们从一行代码开始:var x = 0;。在声明 x 之后(并愉快地附加到 window),这些访问方法中哪种最快,为什么?

  1. 直接在全局范围内

    x = x + 1;
  2. 直接在全局范围内,但以window为前缀

    window.x = window.x + 1;
  3. 在函数内部,不合格

    function accessUnqualified() {
    x = x + 1;
    }
  4. 在函数内部,带有window前缀

    function accessWindowPrefix() {
    window.x = window.x + 1;
    }
  5. 在函数内部,缓存窗口作为变量,前缀访问(模拟 IIFE 的本地参数)。

    function accessCacheWindow() {
    var global = window;
    global.x = global.x + 1;
    }
  6. 在 IIFE 内(窗口作为参数),前缀访问。

     (function(global){
    global.x = global.x + 1;
    })(window);
  7. 在 IIFE 内(窗口作为参数),无限制访问。

     (function(global){
    x = x + 1;
    })(window);

请假设浏览器上下文,即 window 是全局变量。

我编写了一个快速时间测试来循环递增操作一百万次,并对结果感到惊讶。我发现了什么:

                             Firefox          Chrome
------- ------
1. Direct access 848ms 1757ms
2. Direct window.x 2352ms 2377ms
3. in function, x 338ms 3ms
4. in function, window.x 1752ms 835ms
5. simulate IIFE global.x 786ms 10ms
6. IIFE, global.x 791ms 11ms
7. IIFE, x 331ms 655ms

我重复了几次测试,数字似乎具有指示性。但他们让我感到困惑,因为他们似乎暗示:

  • window 为前缀要慢得多(#2 vs #1,#4 vs #3)。但是为什么
  • 在函数中访问全局变量(假设是额外范围查找)更快(#3 vs #1)。 为什么??
  • 为什么 #5、#6、#7 在两种浏览器中的结果如此不同?

我知道有些人认为此类测试对于性能调整毫无意义,这很可能是真的。但是,为了知识的缘故,请幽默一下,帮助我提高对变量访问和作用域链等这些简单概念的理解。

如果您已经阅读到这里,感谢您的耐心等待。很抱歉发了这么长的帖子,并且可能将多个问题归为一个问题 - 我认为它们都有些相关。


编辑:按要求分享我的基准测试代码。

var x, startTime, endTime, time;

// Test #1: x
x = 0;
startTime = Date.now();
for (var i=0; i<1000000; i++) {
x = x + 1;
}
endTime = Date.now();
time = endTime - startTime;
console.log('access x directly - Completed in ' + time + 'ms');

// Test #2: window.x
x = 0;
startTime = Date.now();
for (var i=0; i<1000000; i++) {
window.x = window.x + 1;
}
endTime = Date.now();
time = endTime - startTime;
console.log('access window.x - Completed in ' + time + 'ms');

// Test #3: inside function, x
x =0;
startTime = Date.now();
accessUnqualified();
endTime = Date.now();
time = endTime - startTime;
console.log('accessUnqualified() - Completed in ' + time + 'ms');

// Test #4: inside function, window.x
x =0;
startTime = Date.now();
accessWindowPrefix();
endTime = Date.now();
time = endTime - startTime;
console.log('accessWindowPrefix()- Completed in ' + time + 'ms');

// Test #5: function cache window (simulte IIFE), global.x
x =0;
startTime = Date.now();
accessCacheWindow();
endTime = Date.now();
time = endTime - startTime;
console.log('accessCacheWindow() - Completed in ' + time + 'ms');

// Test #6: IIFE, window.x
x = 0;
startTime = Date.now();
(function(window){
for (var i=0; i<1000000; i++) {
window.x = window.x+1;
}
})(window);
endTime = Date.now();
time = endTime - startTime;
console.log('access IIFE window - Completed in ' + time + 'ms');

// Test #7: IIFE x
x = 0;
startTime = Date.now();
(function(global){
for (var i=0; i<1000000; i++) {
x = x+1;
}
})(window);
endTime = Date.now();
time = endTime - startTime;
console.log('access IIFE x - Completed in ' + time + 'ms');


function accessUnqualified() {
for (var i=0; i<1000000; i++) {
x = x+1;
}
}

function accessWindowPrefix() {
for (var i=0; i<1000000; i++) {
window.x = window.x+1;
}
}

function accessCacheWindow() {
var global = window;
for (var i=0; i<1000000; i++) {
global.x = global.x+1;
}
}

最佳答案

Javascript 的优化很糟糕,因为 eval(可以访问本地框架!)。

但是,如果编译器足够聪明,可以检测到 eval 没有任何作用,那么事情就会变得更快。

如果您只有局部变量、捕获变量和全局变量,并且如果您可以假设没有搞乱 eval,那么理论上:

  • 局部变量访问只是在内存中直接访问与局部帧有偏移
  • 全局变量访问只是内存中的直接访问
  • 捕获的变量访问需要双重间接

原因是,如果 x 查找结果是局部的或全局的,那么它将始终是局部的或全局的,因此可以直接使用 mov 访问它rax, [rbp+0x12](本地)或 mov rax, [rip+0x12345678] 全局。没有任何查找。

对于捕获的变量,由于生命周期问题,事情稍微复杂一些。在一个非常常见的实现中(将捕获的变量包裹在单元格中,并在创建闭包时复制单元格地址)这将需要两个额外的间接步骤......例如

mov rax, [rbp]      ; Load closure data address in rax
mov rax, [rax+0x12] ; Load cell address in rax
mov rax, [rax] ; Load actual value of captured var in rax

再一次在运行时不需要“查找”。

所有这些都意味着您观察到的时间是其他因素的结果。对于单纯的变量访问,与缓存或实现细节等其他问题相比,局部变量、全局变量和捕获变量之间的差异非常小(例如,垃圾收集器的实现方式;例如,移动一个需要额外的全局间接寻址).

当然,使用 window 对象访问全局变量是另一回事...我并不感到惊讶它需要更长的时间(window 也需要是一个常规对象)。

关于javascript - 为什么使用 window.variable 访问变量速度较慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31068060/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com