对象/数组在 JavaScript 中的性能如何?(专门针对谷歌V8)

2022-08-30 05:37:34

与JavaScript(特别是Google V8)中的数组和对象相关的性能将非常有趣。我在互联网上的任何地方都找不到关于这个主题的全面文章。

我知道有些对象使用类作为其底层数据结构。如果有很多属性,它有时被视为哈希表?

我也明白数组有时被视为C++数组(即快速随机索引,缓慢删除和调整大小)。而且,其他时候,它们更像对象(快速索引,快速插入/删除,更多内存)。而且,有时它们可能存储为链接列表(即缓慢的随机索引,在开头/结尾快速删除/插入)

JavaScript 中数组/对象检索和操作的精确性能如何?(专门针对谷歌V8)

更具体地说,它对性能的影响:

  • 向对象添加属性
  • 从对象中删除属性
  • 为对象中的属性编制索引
  • 将项添加到数组
  • 从数组中删除项目
  • 为数组中的项目编制索引
  • Calling Array.pop()
  • 调用 Array.push()
  • 调用 Array.shift()
  • Calling Array.unshift()
  • 调用 Array.slice()

任何有关更多详细信息的文章或链接也将不胜感激。:)

编辑:我真的很想知道JavaScript数组和对象如何在引擎盖下工作。此外,V8引擎在什么上下文中“知道”到“切换”到另一种数据结构?

例如,假设我创建了一个数组...

var arr = [];
arr[10000000] = 20;
arr.push(21);

这到底是怎么回事?

或。。。这又如何呢...???

var arr = [];
//Add lots of items
for(var i = 0; i < 1000000; i++)
    arr[i] = Math.random();
//Now I use it like a queue...
for(var i = 0; i < arr.length; i++)
{
    var item = arr[i].shift();
    //Do something with item...
}

对于传统阵列,性能会很糟糕。然而,如果使用了LinkedList...没那么糟糕。


答案 1

我创建了一个测试套件,正是为了探索这些问题(以及更多)存档副本)。

从这个意义上说,您可以在这个50多个测试用例测试器中看到性能问题(这将需要很长时间)。

顾名思义,它还探讨了使用 DOM 结构的本机链表性质的用法。

(目前已关闭,正在重建中)有关此内容的更多详细信息。

摘要如下

  • V8阵列速度快,非常快
  • 数组推/弹出/移位比任何对象等效物快约20倍以上。
  • 令人惊讶的是,它比数组弹出快约6倍,但比对象属性删除快约100倍。Array.shift()
  • 有趣的是,它比近20(动态阵列)到10(固定阵列)的倍数快。Array.push( data );Array[nextIndex] = data
  • Array.unshift(data)比预期慢,并且比添加新属性慢约 5 倍。
  • 使该值清零比在数组中删除它(未定义)快约4倍++。array[index] = nulldelete array[index]
  • 令人惊讶的是,使对象中的值为空比仅删除属性慢约2倍obj[attr] = nulldelete obj[attr]
  • 不出所料,中阵列很慢,非常慢。Array.splice(index,0,data)
  • 令人惊讶的是,已经过优化(没有长度变化),比拼接快100倍Array.splice(index,1,data)Array.splice(index,0,data)
  • 不出所料,divLinkedList在所有扇区上都不如数组,除了删除(它破坏了测试系统)。dll.splice(index,1)
  • 最大的惊喜[正如jjrv所指出的],V8数组写入速度略快于V8读取=O

注意:这些衡量指标仅适用于 v8 无法“完全优化”的大型数组/对象。对于小于任意大小(24?)的数组/对象大小,可以有非常孤立的优化性能情况。更多细节可以在几个谷歌IO视频中广泛看到。

注2:这些出色的性能结果不会在浏览器之间共享,尤其是IE。此外,测试是巨大的,因此我还没有完全分析和评估结果:请在=中编辑它)*cough*

更新说明(2012 年 12 月):谷歌代表在YouTube上有视频,描述了chrome本身的内部工作原理(例如当它从linkedlist数组切换到固定数组时等),以及如何优化它们。请参阅 GDC 2012:从主机到 Chrome 了解更多信息。


答案 2

在 JavaScript 领域内的基本级别上,对象的属性是更复杂的实体。您可以使用 setters/getter 创建具有不同可枚举性、可写性和可配置性的属性。数组中的项无法以这种方式进行自定义:它要么存在,要么不存在。在底层引擎级别,这允许在组织表示结构的内存方面进行更多的优化。

在从对象(字典)中识别数组方面,JS引擎总是在两者之间建立显式的线条。这就是为什么有很多文章试图制作一个半假的类似Array的对象,它的行为类似于一个对象,但允许其他功能。这种分离甚至存在的原因是因为JS引擎本身以不同的方式存储两者。

属性可以存储在数组对象上,但这只是演示了JavaScript如何坚持使所有内容都成为对象。数组中索引值的存储方式不同于您决定在表示基础数组数据的数组对象上设置的任何属性。

每当您使用合法数组对象并使用操作该数组的标准方法之一时,您都将命中基础数组数据。特别是在 V8 中,这些规则本质上与C++数组相同,因此这些规则将适用。如果由于某种原因,您正在使用一个引擎无法自信地确定是数组的数组,那么您的基础就更加摇摇欲坠。不过,对于最新版本的 V8,还有更多的办公空间。例如,可以创建一个以 Array.prototype 作为其原型的类,并且仍然可以有效地访问各种本机数组操作方法。但这是最近的变化。

指向最近对数组操作的更改的特定链接可能会在这里派上用场:

另外,这里是直接从 V8 源代码实现的 Array Pop 和 Array Push,两者都是在 JS 本身中实现的:

function ArrayPop() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.pop"]);
  }

  var n = TO_UINT32(this.length);
  if (n == 0) {
    this.length = n;
    return;
  }
  n--;
  var value = this[n];
  this.length = n;
  delete this[n];
  return value;
}


function ArrayPush() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.push"]);
  }

  var n = TO_UINT32(this.length);
  var m = %_ArgumentsLength();
  for (var i = 0; i < m; i++) {
    this[i+n] = %_Arguments(i);
  }
  this.length = n + m;
  return this.length;
}