【问题标题】:Should I statically allocate Javascript strings for memory performance?我应该为内存性能静态分配 Javascript 字符串吗?
【发布时间】:2015-05-14 08:38:23
【问题描述】:

所以我正在编写这个 node.js 应用程序,并且我试图让它超快并且内存占用少。我有很多字符串连接正在进行,功能如下:

function f(pt) { 
    return pt.x + ' + ' + pt.y; 
}

如果我在应用程序的内部循环中执行 1 亿次这样的操作,Javascript 引擎是否会分配并必须释放该字符串 ' + ' 1 亿次?将这段代码重写为类似的东西在内存方面是否会更高效

var plus = ' + ';
function f(pt) { 
    return pt.x + plus + pt.y; 
}

或者编译器是否只是在后台执行此操作,或者它甚至无关紧要? (我的代码实际上使用了比 ' + ' 长得多的字符串,我只是以它为例。)

【问题讨论】:

    标签: javascript node.js performance


    【解决方案1】:

    视情况而定。

    但老实说,它可能会慢一点。
    这是因为它必须在函数范围内搜索变量plus,然后是窗口对象,然后在上面的范围内找到它。

    所以,我相信它可能会更慢。

    考虑以下代码:

    console.time('plus outside');
    var plus=' x ', fn=function(pt){return pt.x + plus + pt.y};
    for(var i=0; i<1e5; i++){ fn({x:5,y:6}); }
    console.timeEnd('plus outside');
    

    还有这段代码:

    console.time('plus inside');
    var fn=function(pt){return pt.x + ' + ' + pt.y};
    for(var i=0; i<1e5; i++){ fn({x:5,y:6}); }
    console.timeEnd('plus inside');
    

    在 Google Chrome v41.0.2272.89 m 上同时运行,plus outside 耗时 200 毫秒,而内部耗时 175毫秒!
    速度提高了 25%!

    在下面,您可以看到大致相同时间的打印屏幕:

    你也可以在电脑上测试!

    window.onload=function(){
    
    	(function(elem){
    		
    		var plus=' + ',fn=function(pt){return pt.x + plus + pt.y}, start=new Date();
          
    		for(var i=0; i<1.5e7; i++){ fn({x:5,y:6}); };
    		var end=new Date();
    		
    		elem.innerHTML=(end-start)+'ms (i:'+i+')';
    		
    	})(document.getElementById('plus_outside'));
      
      
      
    	
    	(function(elem){
    		
    		var fn=function(pt){return pt.x + ' + ' + pt.y}, start=new Date();
          
    		for(var i=0; i<1.5e7; i++){ fn({x:5,y:6}); };
    		var end=new Date();
    		
    		elem.innerHTML=(end-start)+'ms (i:'+i+')';
    		
    	})(document.getElementById('plus_inside'));
      
      
    	
    	(function(elem){
    		
    		var fn=function(pt){return [pt.x,'+',pt.y].join(' ')}, start=new Date();
          
    
    		for(var i=0; i<2e5; i++){ fn({x:5,y:6}); };
    		var end=new Date();
    		
    		elem.innerHTML=(end-start)+'ms (i:'+i+')';
    		
    	})(document.getElementById('array'));
      
      
      	(function(elem){
    		
    		var fn=function(pt){return [pt.x,' + ',pt.y].join('')}, start=new Date();
          
    
    		for(var i=0; i<2e5; i++){ fn({x:5,y:6}); };
    		var end=new Date();
    		
    		elem.innerHTML=(end-start)+'ms (i:'+i+')';
    		
    	})(document.getElementById('array_nojoin'));
      
      
      
       (function(elem){
    		
    		var fn=function(pt){return ([pt.x,'+',pt.y]+'').replace(',',' ')}, start=new Date();
          
    
    		for(var i=0; i<2e5; i++){ fn({x:5,y:6}); };
    		var end=new Date();
    		
    		elem.innerHTML=(end-start)+'ms (i:'+i+')';
    		
    	})(document.getElementById('array_replace'));
      
    
    };
    <font face="sans-serif">
    <p>
      Plus inside: <span id="plus_inside"></span><br>
      &nbsp;&nbsp;fn: <code>function(pt){return pt.x + ' + ' + pt.y}</code>
    </p>
    
    <p>
      Plus outside: <span id="plus_outside"></span><br>
      &nbsp;&nbsp;fn: <code>function(pt){return pt.x + plus + pt.y}</code>
    </p>
    
    <p>
      Array: <span id="array"></span><br>
      &nbsp;&nbsp;fn: <code>function(pt){return [pt.x,'+',pt.y].join(' ')}</code>
    </p>
    
    <p>
      Array (no join): <span id="array_nojoin"></span><br>
      &nbsp;&nbsp;fn: <code>function(pt){return [pt.x,' + ',pt.y].join('')}</code>
    </p>
    
    
    <p>
      Array (replace comas): <span id="array_replace"></span><br>
      &nbsp;&nbsp;fn: <code>function(pt){return ([pt.x,'+',pt.y]+'').replace(',',' ')}</code>
    </p>
    </font>

    【讨论】:

    • 不知道console.timeconsole.timeEnd。谢谢。
    • @victorkohl 不客气。这种方法比使用 2 个日期并减去它们要可靠得多。它可以计算到皮秒的时间。我在 2 年前看到了这个技巧,我在时机很关键的时候使用它。遗憾的是,它并非随处可用(例如:IE8)。但这是一个不错的功能,并且可以很好地输出时间。
    • 根据我的计算快了 12.5%,但仍然感谢您的洞察力!几乎做了一堆会使我的代码变慢的工作。
    • @InquisitiveIdiot 不客气。您可以尝试的一件事是将重复的字符串设置在同一范围内。由于字符串在 Javascript 中是不可变的,因此它可能没有多大帮助,但值得一试。
    【解决方案2】:

    可以通过将字符串推送到数组来加速字符串连接。然后加入数组。

    ["a","b","c"].join('');
    

    这是因为 join 是一个调用,而且编译器可以立即计算结果字符串的全长。

    【讨论】:

    • 虽然这不能解决问题,但很高兴知道这一点。
    • 当字符串由许多子字符串组成时也很好:pushS1().pushS2().pushS3() 等(这些 fns 返回一个内部包含数组的对象和这些成员 fn)。
    【解决方案3】:

    这将取决于实现,但至少就 V8 (Chrome) 而言,不,每个函数调用似乎都不会实例化一个新的' + '。很可能函数体被编译成一个一次性完成连接的操作。

    您可以通过首先定义一个函数(在控制台中)来看到这一点:

    var f = function(a, b) { return a + ' + ' + b; };
    

    然后启动堆分析器并执行:

    var c = f('1', '2');
    

    分析器显示的是在这段时间内只分配了一个字符串:

    "1 + 2"
    

    这似乎意味着它甚至没有将两个单独的连接操作视为单独的操作。一口气搞定。

    底线:像这样的微优化不太可能让您走得更远。正如 Ismael 所说,它实际上可能会减慢您的代码速度。

    【讨论】:

      猜你喜欢
      • 2018-06-16
      • 1970-01-01
      • 1970-01-01
      • 2020-11-28
      • 2013-11-10
      • 2016-04-29
      • 2019-07-12
      • 2014-12-06
      • 1970-01-01
      相关资源
      最近更新 更多