当我研究快速属性访问的 v8 设计元素时,它在本主题的最后一段中提到了这一点:
使用隐藏类有两个优点:属性访问不需要字典查找,它们使 V8 能够使用经典的基于类的优化,内联缓存。
这对我来说有点模糊。谁能详细说明为什么隐藏类不需要字典查找并使 v8 能够使用经典的基于类的优化,内联缓存?
请尽可能详细地执行此操作。
当我研究快速属性访问的 v8 设计元素时,它在本主题的最后一段中提到了这一点:
使用隐藏类有两个优点:属性访问不需要字典查找,它们使 V8 能够使用经典的基于类的优化,内联缓存。
这对我来说有点模糊。谁能详细说明为什么隐藏类不需要字典查找并使 v8 能够使用经典的基于类的优化,内联缓存?
请尽可能详细地执行此操作。
我认为您提到的文章中很好地解释了隐藏类的概念。每当向对象添加新属性时,就会创建一个新的隐藏类。该对象保持对这个隐藏类的引用。此外,隐藏类还保留了对先前创建的隐藏类的引用。例如:
function Point(x, y) {
this.x = x;
this.y = y;
}
var point = new Point(10, 10);
当new Point()
被调用时,会创建一个新的隐藏类,并point
保留对该隐藏类的引用。最初的隐藏类是空的,这意味着它不包含任何属性。然后,当this.x
被调用时,会创建一个新的隐藏类。该隐藏类保留对前一个隐藏类的引用,并更新其对新隐藏类的引用。执行时再次发生同样的情况this.y = y
。
就像在 JavaScript 中可以动态地向对象添加和删除属性一样,解决属性访问的方法是使用map。另一方面,隐藏类一个接一个地线性存储属性,就像 C 中的结构一样。多亏了隐藏类,访问属性与访问数组元素一样快。
现在让我们看看内联缓存是什么意思。内联缓存是一种古老的优化技术,用于 Smalltak 80 或 Self 等动态语言,并由 JavaScript 推广。在运行时访问属性时,需要确定调用对象的类型,以便准确了解要调用的实现代码。这称为动态分派或后期绑定,是 JavaScript 在访问属性或对两个操作数求和时发生的情况(它们可能是integer、double等)。考虑以下代码:
var x = 10;
var y = 10;
var total = x + y;
当使用内联缓存时var total = x + y
,编译时,您不会在对通用添加子例程的过程调用中进行编译。相反,该代码被编译为存根(内联缓存)。在内联缓存中生成的代码会查看接收到的参数的类型,并生成专门针对这些类型的代码。稍后,如果调用另一个添加,则假定类型将与以前相同,则执行内联缓存。但是,可能会发生类型不同的情况,因此内联缓存所做的第一件事就是检查参数的类型。如果类型不同导致缓存未命中,并为这些特定类型生成新代码。如果内联缓存可以处理许多不同的类型,则称为多态内联缓存(一个具有多于一组类型的条目)。
内联缓存节省了在函数调用中推断参数类型的计算,如果调用发生在循环内,它们的好处就更大。
内联缓存发生在 JavaScript 中访问对象的属性时,在我们看到隐藏类如何帮助我们快速访问属性之前。
有关隐藏类和内联缓存的更多信息,我推荐以下文章(特别是前两篇,来自 V8 黑客 Vyacheslav Egorov):