为什么使用window.variable访问变量较慢? [英] Why is accessing a variable using window.variable slower?
问题描述
我们期望今天:
(function($,window,undefined){
//显然,这里的变量访问速度比外面的要快IIFE
})(jQuery,窗口);
简化/扩展这个广义的情况,可以预料到:
var x = 0;
(函数(窗口){
//在这里访问window.x应该更快
})(window);
基于我对JS的理解, x = 1 ;全局范围内的
和 window.x = 1;
。因此,期望它们具有同样的性能是合乎逻辑的,对吗? 错误我运行了一些测试,发现访问时间存在显着差异。
好的,也许如果我把 window.x = 1;
在一个IIFE中,它应该运行得更快(即使只是略微),对吧? 错误再次。
好的,也许是Firefox;让我们来试试Chrome吧(V8是JS速度的基准,是吧?)它应该击败Firefox来获得简单的东西,比如直接访问全局变量,对吗? 再次错误。
因此,我着手在两个浏览器的每一个中准确找出哪种访问方法是最快的。假设我们从一行代码开始: var x = 0;
。在 x
已被声明(并很高兴地附加到窗口
)之后,这些访问方法中的哪一个将是最快的,为什么?
-
直接在全局范围内
x = x + 1;
-
直接在全局范围内,但前缀为
窗口
$ p $window.x = window.x + 1;
-
在一个函数中,不合格的
function accessUnqualified(){
x = x + 1;
$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $' / code>前缀
函数accessWindowPrefix(){
window.x = window.x + 1;
$ b $ li
$里面的函数,缓存窗口作为变量,前缀访问(模拟
函数accessCacheWindow(){
var global = window;
global.x = global.x + 1;
$ i
$ i $ i $ i $ i $ b $ i / p>
(function(global){
global.x = global.x + 1;
})(窗口);
-
在一个IIFE(作为参数的窗口)中,不合格的访问。
$ b(function(global){
x = x + 1;
})(window);
请假设浏览器环境,即<$ c
我写了一个快速的时间测试循环增量操作一百万次,并感到惊讶结果。我发现:
Firefox Chrome
------- ------
1.直接访问848ms 1757ms
2.直接window.x 2352ms 2377ms
3.在函数中,x 338ms 3ms
4.在函数中,window.x 1752ms 835ms
5 。simulate IIFE global.x 786ms 10ms
6. IIFE,global.x 791ms 11ms
7. IIFE,x 331ms 655ms
我重复了几次测试,数字似乎是指示性的。但他们对我感到困惑,因为他们似乎建议:
- 以
窗口为前缀
慢得多(#2与#1,#4与#3)。但是 WHY ? - 在函数中访问全局(据推测,额外的作用域查找)速度更快(#3 vs#1)。 为什么 ??
- 为什么在这两个浏览器中#5,#6,#7的结果如此不同? ul>
据我所知,有些人认为这样的测试对于性能调优毫无意义,这也许是事实。但是,为了知识的缘故,请为我提供一些幽默,帮助提高对这些简单概念的理解,比如变量访问和范围链。
如果您已经阅读了这些内容,感谢您的耐心等待。对这个长文章道歉,也可能把多个问题归结为一个 - 我认为它们都有些相关。 strong>编辑:按要求共享我的基准测试代码。
var x,startTime,endTime,time ; //测试1:xx = 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.xx = 0; startTime = Date.now(); for(var i = 0 ;< 1000000; i ++){window.x = window.x + 1;} endTime = Date.now(); time = endTime - startTime; console.log('access window.x - Completed in'+ time +' ms); //测试#3:内函数,xx = 0; startTime = Date.now(); accessUnqualified(); endTime = Date.now(); time = endTime - st (); timeTime; console.log('accessUnqualified() - '+ time +'ms'); // Test#4:inside function,window.xx = 0; startTime = Date.now(); accessWindowPrefix(); endTime msg); //测试5:函数缓存窗口(simulte IIFE),global.xx() msgstr); / / msgs =0; startTime = Date.now(); accessCacheWindow(); endTime = Date.now(); time = endTime - startTime; console.log('accessCacheWindow() / Test#6:IIFE,window.xx = 0; startTime = Date.now();(function(window){for(var i = 0; I< 1000000; i ++){window.x = window.x + 1; (测试#7:IIFE xx =+); //测试#7:IIFE xx = 0; startTime = Date.now();(function(global){for(var i = 0; i <1000000; i ++){x = x + 1;}})(window); endTime = Date.now ;函数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; }}函数accessCacheWindow(){var global = window; for(var i = 0; i <1000000; i ++){global.x = global.x + 1; }}
解决方案因为
eval
(可以访问本地框架!),所以对于优化来说是非常糟糕的。
如果编译器是足够聪明地发现
eval
没有任何作用,那么事情会变得更快。
如果只有本地变量,被捕获的变量和全局变量,如果你可以假设没有搞乱
eval
那么理论上是这样的:
- 局部变量访问只是内存中的一个直接访问,与本地框架的偏移量
- 全局变量访问只是内存中的一个直接访问
- 捕获的变量访问需要双重间接
原因是如果
x
在查找结果为本地或全局时,它将始终是本地或全局的,因此可以直接使用mov rax,[ RBP +为0x1 2]
(当一个本地)或mov rax,[rip + 0x12345678]
时全局。没有查找任何。
对于捕获的变量,由于生命期问题,事情稍微复杂一些。在一个非常常见的实现中(一个被捕获的变量被封装在单元格和被创建的闭包中复制的单元格),这将需要两个额外的间接步骤...即例如
mov rax,[rbp];在rax中加载关闭数据地址
mov rax,[rax + 0x12];传感器地址在rax
mov rax,[rax];在rax中加载被捕获的变量的实际值
运行时不需要查找。 >
所有这些意味着你观察的时间是其他因素的结果。对于单纯的变量访问,局部变量,全局变量和捕获变量之间的差异与其他问题(如缓存或实现细节)相比非常微小(例如,垃圾收集器是如何实现的;例如移动的变量将需要额外的全局变量)。
当然,使用
窗口访问全局对象是另一回事非常惊讶,它需要更长的时间(
窗口
也是一个常规对象)。Multiple sources for JS performance tips encourage developers to reduce "scope chain lookup". For example, IIFEs are touted as having a bonus benefit of "reducing scope chain lookup" when you access global variables. This sounds quite logical, perhaps even taken for granted, so I didn't question the wisdom. Like many others, I have been happily using IIFEs thinking that on top of avoiding global namespace pollution, there's gonna be a performance boost over any global code.
What we expect today:
(function($, window, undefined) { // apparently, variable access here is faster than outside the IIFE })(jQuery, window);
Simplifying / extending this to a generalized case, one would expect:
var x = 0; (function(window) { // accessing window.x here should be faster })(window);
Based on my understanding of JS, there is no difference between
x = 1;
andwindow.x = 1;
in the global scope. Therefore, it is logical to expect them to be equally performant, right? WRONG. I ran some tests and discovered that there's a significant difference in access times.Ok, maybe if I place the
window.x = 1;
inside an IIFE, it should run even faster (even if just slightly), right? WRONG again.Ok, maybe it's Firefox; let's try Chrome instead (V8 is the benchmark for JS speed, yea?) It should beat Firefox for simple stuff like accessing a global variable directly, right? WRONG yet again.
So I set out to find out exactly which method of access is fastest, in each of the two browsers. So let's say we start with one line of code:
var x = 0;
. Afterx
has been declared (and happily attached towindow
), which of these methods of access would be fastest, and why?Directly in global scope
x = x + 1;
Directly in global scope, but prefixed with
window
window.x = window.x + 1;
Inside a function, unqualified
function accessUnqualified() { x = x + 1; }
Inside a function, with
window
prefixfunction accessWindowPrefix() { window.x = window.x + 1; }
Inside a function, cache window as variable, prefixed access (simulate local param of an IIFE).
function accessCacheWindow() { var global = window; global.x = global.x + 1; }
Inside an IIFE (window as param), prefixed access.
(function(global){ global.x = global.x + 1; })(window);
Inside an IIFE (window as param), unqualified access.
(function(global){ x = x + 1; })(window);
Please assume browser context, i.e.
window
is the global variable.I wrote a quick time test to loop the increment operation a million times, and was surprised by the results. What I found:
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
I repeated the test a few times, and the numbers appear to be indicative. But they are confusing to me, as they seem to suggest:
- prefixing with
window
is much slower (#2 vs #1, #4 vs #3). But WHY? - accessing a global in a function (supposedly extra scope lookup) is faster (#3 vs #1). WHY??
- Why are the #5,#6,#7 results so different across the two browsers?
I understand there are some who think such tests are pointless for performance tuning, and that may well be true. But please, for the sake of knowledge, just humor me and help improve my understanding of these simple concepts like variable access and scope chain.
If you have read this far, thank you for your patience. Apologies for the long post, and for possibly lumping multiple questions into one - I think they are all somewhat related.
Edit: Sharing my benchmark code, as requested.
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 is terrible for optimization because of
eval
(that can access the local frame!).If however the compilers are smart enough to detect that
eval
plays no role then things can get a lot faster.If you only have local variables, captured variables and global variables and if you can assume no messing up with
eval
is done then, in theory:- A local variable access is just a direct access in memory with an offset from the local frame
- A global variable access is just a direct access in memory
- A captured variable access requires a double indirection
The reason is that if
x
when looked up results in a local or in a global then it will always be a local or a global and thus it could be accessed directly say withmov rax, [rbp+0x12]
(when a local) ormov rax, [rip+0x12345678]
when a global. No lookup whatsoever.For captured variables things are slightly more complex because of lifetime issues. On a very common implementation (a captured variables wrapped up in cells and cells copied when creating closures) this will require two extra indirection steps... i.e. for example
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
Once again no "lookup" needed at runtime.
All this means that the timing you are observing is a consequence of other factors. For the mere variable access the difference between a local, a global and a captured variable are very tiny compared to other issues like caching or implementation details (e.g. how the garbage collector is implemented; a moving one for example would require an extra indirection for globals).
Of course accessing a global using the
window
object is another matter... and I'm not very surprised it takes longer (window
is required to be also a regular object).这篇关于为什么使用window.variable访问变量较慢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- 局部变量访问只是内存中的一个直接访问,与本地框架的偏移量