为什么使用window.variable访问变量较慢? [英] Why is accessing a variable using window.variable slower?

查看:268
本文介绍了为什么使用window.variable访问变量较慢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

JS性能技巧的多个来源鼓励开发人员减少范围链查找。例如,当您访问全局变量时,IIFE被吹捧为减少范围链查找的收益优势。这听起来很合乎逻辑,也许甚至被认为是理所当然的,所以我并没有质疑智慧。与其他许多人一样,我一直很高兴地使用IIFE,认为除了避免全球名称空间污染之外,还会有一个性能提升超过任何全球代码。



我们期望今天:

 (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 已被声明(并很高兴地附加到窗口)之后,这些访问方法中的哪一个将是最快的,为什么?


  1. 直接在全局范围内

      x = x + 1; 


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


  3. 在一个函数中,不合格的

      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;
    })(窗口);


  4. 在一个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; and window.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;. After x has been declared (and happily attached to window), which of these methods of access would be fastest, and why?

    1. Directly in global scope

      x = x + 1;
      

    2. Directly in global scope, but prefixed with window

      window.x = window.x + 1;
      

    3. Inside a function, unqualified

      function accessUnqualified() {
          x = x + 1;
      }
      

    4. Inside a function, with window prefix

      function accessWindowPrefix() {
          window.x = window.x + 1;
      }
      

    5. 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;
      }
      

    6. Inside an IIFE (window as param), prefixed access.

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

    7. 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 with mov rax, [rbp+0x12] (when a local) or mov 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆