在JavaScript中读取数组的`length`属性真是那么昂贵吗?

ale*_*lex 65 javascript arrays

我总是假设在JavaScript中缓存数组的长度是一个好主意(特别是在for循环的条件下),因为计算数组的长度是昂贵的.

for (var i = 0; i < arr.length; i++) { }

// vs

for (var i = 0, arrLength = arr.length; i < arrLength; i++) { }
Run Code Online (Sandbox Code Playgroud)

但是,我想也许该length属性只是在创建和更改数组时更新.因此,阅读它不应该是一个操作太昂贵而不是读取存储在变量中的操作(与其他语言中可能需要在内存中寻找某些内容的其他方法相反,例如strlen()在C中).

我有两个问题.我也对它是如何工作感兴趣,所以请不要用过早的优化棒打我.

假设浏览器中的JavaScript引擎.

  1. length在JavaScript中缓存数组的属性有什么好处吗?是否有更多的参与在对象的属性上读取局部变量?
  2. length属性是否仅在创建和on上更改,shift()并且pop()类型方法不返回新数组,否则只是存储为整数?

Koo*_*Inc 47

嗯,我会说它很贵,但后来我写了一个小测试@ jsperf.com,令我惊讶的是,使用i<array.lengthChrome实际上更快,而在FF(4)中它并不重要.

我怀疑是长度存储为整数(Uint32).根据ECMA规范(262 ed.5,第121页):

每个Array对象都有一个length属性,其值始终是小于2 32的非负整数.length属性的值在数值上大于名称为数组索引的每个属性的名称; 无论何时创建或更改Array对象的属性,都会根据需要调整其他属性以保持此不变量.具体来说,每当添加名称为数组索引的属性时,如果需要,将更改length属性,使其大于该数组索引的数值; 每当更改length属性时,将自动删除名称为数值索引且值不小于新长度的每个属性.此约束仅适用于Array对象的自身属性,并且不受可能从其原型继承的长度或数组索引属性的影响

唷!我不知道我是否习惯了这种语言......

最后,我们总是让我们的老人落后于浏览器.在IE(9,8,7)中,缓存的长度确实更快.我说,不使用IE的更多原因之一.

  • 脚本语言中的数组(Ruby,Perl,...)通常将两个长度称为简单整数:已分配了多少个槽以及正在使用多少个槽(即长度).任何在JavaScript引擎中以不同方式执行操作的人都不应该被允许在编译器附近的任何地方使用.数组通常知道它们有多长时间没有计算它.当然除了C. (5认同)
  • 我要说,因为缓存长度确实在IE和IE中有所作为(至少在IE 9之前)是非常慢的浏览器,这是一个值得的优化. (4认同)

Dem*_*cht 26

TL; DR:

从我可以收集的内容来看,似乎数组的长度在内部缓存(至少在V8中).

(详情?请继续阅读:))

所以,这个问题已经在我的脑海中爆发了几次,我决定找到问题的根源(至少在一个实现中).

挖掘V8源代码产生了JSArray类.

// The JSArray describes JavaScript Arrays
//  Such an array can be in one of two modes:
//    - fast, backing storage is a FixedArray and length <= elements.length();
//       Please note: push and pop can be used to grow and shrink the array.
//    - slow, backing storage is a HashTable with numbers as keys.
Run Code Online (Sandbox Code Playgroud)

我假设数组元素的类型决定它是快还是慢.我得到了一些设置在set_has_fast_elements(set_bit_field2(bit_field2() | (1 << kHasFastElements)))中的标志,这是我想要绘制挖掘线的地方,因为我在查看谷歌代码并且没有本地源.

现在,它似乎任何时候任何操作是在阵列上完成(这是一个子类的JSObject,就会调用作出NormalizeElements(),其执行以下操作:

// Compute the effective length.
  int length = IsJSArray() ?
      Smi::cast(JSArray::cast(this)->length())->value() :
      array->length();
Run Code Online (Sandbox Code Playgroud)

所以,在回答你的问题时:

  1. Chrome(或其他使用V8的浏览器)缓存length数组的属性似乎没有任何优势(除非你做了一些奇怪的事情会强制它slow(我不确定那些条件是什么) ) - 说了这么多,我很可能继续缓存,length直到我有机会完成所有 OS浏览器的实现;)
  2. 在对象上length进行任何操作后,该属性似乎都会被更改.

编辑:

另外,似乎"空"数组实际上分配了4个元素:

// Number of element slots to pre-allocate for an empty array.
static const int kPreallocatedArrayElements = 4;
Run Code Online (Sandbox Code Playgroud)

一旦超出界限,我不确定阵列增长了多少元素 - 我没深挖那么深的:)

  • 这是一个很长的时间;博士,但是+1用于挖掘浏览器源代码. (7认同)

Anu*_*rag 12

另一组性能测试.循环在一百万个随机数的数组上完成,并带有一个空循环.

在Chrome中,具有缓存和非缓存长度的循环时钟几乎相同,所以我猜测它是一个V8优化来缓存长度.

在Safari和Firefox中,缓存长度始终比非缓存版本快2倍.


joe*_*dle 5

本文通过询问IRHydra生成的代码来研究V8和Chrome中的自动缓存:

Grinch如何通过Vyacheslav Egorov 窃取array.length

他发现在某些条件下,手动缓存.length实际上增加的开销而不是提高性能!

但是无论如何,这种微优化不可能为您的用户带来任何明显的收益。为了他们和您的利益,请专注于易于阅读的代码,并在代码中使用良好的数据结构和算法!

避免过早的优化:关注优美的代码,直到出现性能问题。只有这样,才能通过性能分析找出瓶颈,然后仅对代码的那部分进行优化。