JavaScript性能:多个变量还是一个对象?

时间:2012-01-09 14:30:17

标签: javascript performance

这只是一个简单的性能问题,帮助我理解javascript引擎。 为此,我想知道,更快的是:为某些值声明多个变量或使用一个包含多个值的对象。

示例:

var x = 15;
var y = 300;

VS

var sizes = { x: 15, y: 300 };

这只是一个非常简单的例子,当然可以在一个真实的项目中有所不同。 这甚至不重要吗?

4 个答案:

答案 0 :(得分:32)

这个问题的完整答案真的很长。所以我会尝试解释一些事情。首先,也许是最重要的事实,即使您使用var声明变量,它也取决于您执行此操作的位置。在全局范围内,您隐式地也会在对象中编写该变量,大多数浏览器都将其称为window。所以例如

// global scope
var x = 15;

console.log( window.x ); // 15

如果我们在一个函数的上下文中做同样的事情就会发生变化。在函数的上下文中,我们将该变量名称写入其称为“激活对象”的名称中。也就是说,js引擎为你处理的内部对象。所有形式参数,函数声明和变量都存储在那里。

现在回答你的实际问题:在函数的上下文中,它总是以var声明变量的最快访问权限。如果我们处于全球背景下,这又不是真的。全局对象非常庞大,访问内部的任何内容都不是很快。

如果我们将事物存储在一个对象中,它仍然非常快,但不如var声明的变量快。特别是访问时间确实增加了。但是,我们在这里谈论微观和纳秒(在现代浏览器实现中)。旧的浏览器,尤其是IE6 + 7,在访问对象属性时会有巨大的性能损失。

如果你真的对这样的东西感兴趣,我会推荐Nicholas C. Zakas写的“高性能Javascript ”一书。他为您测量了许多不同的技术来访问和存储ECMAscript中的数据。

同样,var声明的对象查找和变量的性能差异在现代浏览器中几乎无法衡量。像FF3或IE6这样的Old'ish浏览器确实显示出对象查找/访问的基本缓慢性能。

答案 1 :(得分:6)

你绝对是微观优化。在存在可证明的性能瓶颈之前我不会担心它,并且您已经将问题范围缩小到使用多个vars与具有属性的对象。

使用对象方法逻辑思考它需要三个变量创建,一个用于对象,一个用于对象上的每个属性,而2用于声明变量。因此拥有该对象将具有更高的内存方法。然而,将对象传递给方法可能比n> 1更有效。 1个变量到一个方法,因为你只需要复制1个值(javascript是按值传递)。这也有助于跟踪对象的词法范围;即将更少的东西传递给方法将使用更少的内存。

但是,我怀疑性能差异甚至可以被任何分析器量化。

答案 2 :(得分:3)

在所有现代浏览器(IE11 + / Edge和任何版本的Chrome,FireFox和Safari)和NodeJS中,

foo_bar总是比foo.bar更快,只要您将性能视为整体即可(我建议你应该)。在紧密循环中进行了数百万次迭代后,由于拥有大量正确的分支预测,foo.bar可能会(但绝不会超过)与foo_bar相同的操作速度。 foo.bar在JIT编译和执行期间会产生大量的开销,因为它的操作非常复杂。没有紧密循环的JavaScript可以从使用foo_bar中受益,因为与之相比,foo.bar的开销:储蓄率要高得多,因此{{1 }}只是为了使foo.bar在某些地方更快。当然,所有JIT引擎都会聪明地尝试猜测在优化内容方面要付出多少努力,以最大程度地减少不必要的开销,但是处理foo.bar仍然会产生基准开销,而这些开销永远都不会被优化掉。

为什么? JavaScript是一种高度动态的语言,其中与每个对象相关的开销很大。它最初只是逐行执行的微小脚本,但仍然表现出逐行执行行为(不再逐行执行,但是,例如,可以执行foo.bar之类的邪恶操作来记录日志20)。 JIT编译受到JavaScript必须遵守逐行行为这一事实的严重限制。 JIT并非可以预期所有事情,因此,为了使如下所示的无关代码无法正常运行,所有代码都必须缓慢。

var a=10;eval('a=20');console.log(a)

(function() {"use strict";
// chronological optimization is very poor because it is so complicated and volatile
var setTimeout=window.setTimeout;
var scope = {};
scope.count = 0;
scope.index = 0;
scope.length = 0;

function increment() {
 // The code below is SLOW because JIT cannot assume that the scope object has not changed in the interum
 for (scope.index=0, scope.length=17; scope.index<scope.length; scope.index=scope.index+1|0)
   scope.count = scope.count + 1|0;
 scope.count = scope.count - scope.index + 1|0;
}

setTimeout(function() {
  console.log( scope );
}, 713);

for(var i=0;i<192;i=i+1|0)
  for (scope.index=11, scope.length=712; scope.index<scope.length; scope.index=scope.index+1|0)
    setTimeout(increment, scope.index);
})();

通过将每个代码段运行30次以上并查看哪个代码段的计数更高,来执行一个样本z间隔,我有90%的信心,带有纯变量名的后面的代码段比带有对象的第一个代码段要快在76.5%到96.9%的时间内访问。作为分析数据的另一种方法,我收集的数据是fl幸的机会为0.0000003464%,而第一个代码段实际上更快。因此,我认为推断(function() {"use strict"; // chronological optimization is very poor because it is so complicated and volatile var setTimeout=window.setTimeout; var scope_count = 0; var scope_index = 0; var scope_length = 0; function increment() { // The code below is FAST because JIT does not have to use a property cache for (scope_index=0, scope_length=17; scope_index<scope_length; scope_index=scope_index+1|0) scope_count = scope_count + 1|0; scope_count = scope_count - scope_index + 1|0; } setTimeout(function() { console.log({ count: scope_count, index: scope_index, length: scope_length }); }, 713); for(var i=0;i<192;i=i+1|0) for (scope_index=4, scope_length=712; scope_index<scope_length; scope_index=scope_index+1|0) setTimeout(increment, scope_index); })();foo_bar更快是合理的,因为开销较小。

别误会我的意思。哈希映射非常快,因为许多引擎都具有高级属性高速缓存,但是使用哈希映射时,总会有足够的额外开销。观察。

foo.bar

要进行性能比较,请观察通过数组和局部变量进行的传递引用。

(function(){"use strict"; // wrap in iife

// This is why you should not pack variables into objects
var performance = window.performance; 

var iter = {};
iter.domino = -1; // Once removed, performance topples like a domino
iter.index=16384, iter.length=16384;
console.log(iter);


var startTime = performance.now();

// Warm it up and trick the JIT compiler into false optimizations
for (iter.index=0, iter.length=128; iter.index < iter.length; iter.index=iter.index+1|0)
  if (recurse_until(iter, iter.index, 0) !== iter.domino)
    throw Error('mismatch!');

// Now that its warmed up, drop the cache off cold and abruptly
for (iter.index=0, iter.length=16384; iter.index < iter.length; iter.index=iter.index+1|0)
  if (recurse_until(iter, iter.index, 0) !== iter.domino)
    throw Error('mismatch!');

// Now that we have shocked JIT, we should be running much slower now
for (iter.index=0, iter.length=16384; iter.index < iter.length; iter.index=iter.index+1|0)
  if (recurse_until(iter, iter.index, 0) !== iter.domino)
    throw Error('mismatch!');

var endTime=performance.now();

console.log(iter);
console.log('It took ' + (endTime-startTime));

function recurse_until(obj, _dec, _inc) {
  var dec=_dec|0, inc=_inc|0;
  var ret = (
    dec > (inc<<1) ? recurse_until(null, dec-1|0, inc+1|0) :
    inc < 384 ? recurse_until :
    // Note: do not do this in production. Dynamic code evaluation is slow and
    //  can usually be avoided. The code below must be dynamically evaluated to
    //  ensure we fool the JIT compiler.
    recurse_until.constructor(
      'return function(obj,x,y){' +
          // rotate the indices
          'obj.domino=obj.domino+1&7;' +
          'if(!obj.domino)' +
          'for(var key in obj){' +
              'var k=obj[key];' +
              'delete obj[key];' +
              'obj[key]=k;' +
              'break' +
          '}' +
          'return obj.domino' +
      '}'
    )()
  );
  if (obj === null) return ret;
  
  recurse_until = ret;
  return obj.domino;
}

})();

JavaScript与其他语言有很大的不同,基准被滥用时很容易成为性能的罪魁祸首。真正重要的是,理论上应该以最快的速度处理JavaScript中的所有内容。您现在正在运行基准测试的浏览器可能无法针对更高版本的浏览器进行优化。

此外,在我们编程的方向上引导浏览器。如果每个人使用的CodeA都不会对纯逻辑造成任何性能影响,但是只有在特定浏览器中才真正快(44Kops / s),其他浏览器将倾向于优化Code​​A,而CodeA最终可能在所有浏览器中超过44Kops / s。另一方面,如果CodeA在所有浏览器中的运行速度确实很慢(9Kops / s),但在逻辑上是非常合理的,则浏览器将能够利用该逻辑,而CodeA很快将在所有浏览器中超过900Kops / s。确定代码的逻辑性能非常简单且非常困难。人们必须把自己放在计算机的鞋子里,然后想象一个人有无限数量的纸张,无限量的铅笔和无限长的时间,并且没有能力解释代码的目的/意图强>。在这样的假设情况下,如何组织代码以发挥最佳性能?例如,假设// This is the correct way to write blazingly fast code (function(){"use strict"; // wrap in iife var performance = window.performance; var iter_domino=[0,0,0]; // Now, domino is a pass-by-reference list var iter_index=16384, iter_length=16384; var startTime = performance.now(); // Warm it up and trick the JIT compiler into false optimizations for (iter_index=0, iter_length=128; iter_index < iter_length; iter_index=iter_index+1|0) if (recurse_until(iter_domino, iter_index, 0)[0] !== iter_domino[0]) throw Error('mismatch!'); // Now that its warmed up, drop the cache off cold and abruptly for (iter_index=0, iter_length=16384; iter_index < iter_length; iter_index=iter_index+1|0) if (recurse_until(iter_domino, iter_index, 0)[0] !== iter_domino[0]) throw Error('mismatch!'); // Now that we have shocked JIT, we should be running much slower now for (iter_index=0, iter_length=16384; iter_index < iter_length; iter_index=iter_index+1|0) if (recurse_until(iter_domino, iter_index, 0)[0] !== iter_domino[0]) throw Error('mismatch!'); var endTime=performance.now(); console.log('It took ' + (endTime-startTime)); function recurse_until(iter_domino, _dec, _inc) { var dec=_dec|0, inc=_inc|0; var ret = ( dec > (inc<<1) ? recurse_until(null, dec-1|0, inc+1|0) : inc < 384 ? recurse_until : // Note: do not do this in production. Dynamic code evaluation is slow and // can usually be avoided. The code below must be dynamically evaluated to // ensure we fool the JIT compiler. recurse_until.constructor( 'return function(iter_domino, x,y){' + // rotate the indices 'iter_domino[0]=iter_domino[0]+1&7;' + 'if(!iter_domino[0])' + 'iter_domino.push( iter_domino.shift() );' + 'return iter_domino' + '}' )() ); if (iter_domino === null) return ret; recurse_until = ret; return iter_domino; } })();产生的哈希映射比执行foo.bar慢一点,因为foo_bar需要查看名为foo的表并找到名为bar的属性。您可以将手指放在bar属性的位置上以对其进行缓存,但是查看表以查找bar花费时间的开销。

答案 3 :(得分:2)

理论或问题,例如“你是什么......嗯......做,伙计?”,当然,这里可以作为答案出现。但我不认为这是好方法。

我刚创建了两个测试平台:

  1. 特定,全球范围http://jsben.ch/SvNyw
  2. 例如,它显示在2017年7月的 Chromium 浏览器中( Vivaldi Opera Google Chrome < / strong>和其他)为了达到最大性能,最好使用 var 。它的读取速度快25%,写入速度快10%。

    Node.js 下,由于相同的JS引擎,结果大致相同。

    Opera Presto( 12 .18)中,测试结果的百分比与基于铬的浏览器相似。

    在(现代) Firefox 中还有其他奇怪的图片。读取全局范围var与读取对象属性大致相同,全局范围var的写入比写入obj.prop(大约慢两倍)慢得多。这似乎是个bug。

    IE / Edge 或其他任何人的测试下,欢迎您使用。

    1. 正常情况下,http://jsben.ch/5UvSZ用于函数内本地范围
    2. 在基于Chromium的浏览器和Mozilla Firefox中,根据对象属性访问,您可以看到对简单var性能的巨大支配。局部简单变量比处理对象属性快几倍(!)。

      所以,

      如果您需要最大化一些关键的JavaScript代码性能:

        浏览器中的
      • - 您可以强制为多个浏览器进行不同的优化。 我不推荐!或者你可以选择一些“最喜欢的”浏览器,为它优化你的代码而不会看到其他的冻结。不是很好,但是就是这样。

      • 再次在浏览器中
      • - 你真的需要优化这种方式吗?您的算法/代码逻辑可能有问题吗?

      • 高负载 Node.js模块(或其他高负载计算)中
      • - 好吧,尽量减少对象“点”,最小化对质量/可读性的损害 - 使用{{1 }}

      任何情况下的安全优化技巧 - 当您使用var进行过多操作时,您可以执行obj.subobj.*并使用var subobj = obj.subobj;进行操作。这可以提高可读性。

      在任何情况下,请考虑您需要做什么,并为您的高负载代码制作真正的标记。