Javascript 在V8中动态对象访问是如何工作的?
我惊讶地发现JavaScript对象。据我所知,获取和设置对象的属性很快,因为值的内存位置处于固定偏移量,就像在结构或类中一样。我不明白的是语法是如何映射到固定偏移量的。即当编译器看到Javascript 在V8中动态对象访问是如何工作的?,javascript,v8,Javascript,V8,我惊讶地发现JavaScript对象。据我所知,获取和设置对象的属性很快,因为值的内存位置处于固定偏移量,就像在结构或类中一样。我不明白的是语法是如何映射到固定偏移量的。即当编译器看到obj.a或obj['a']时会发生什么。该语法是否在运行时、编译时或JIT时转换为整数偏移量?我想我试图理解的是,它如何能够有效地将传入字符串“a”转换为整数索引,而不必执行类似于index=hash('a')%objectLength的操作 也许我知识上的差距在于我没有完全理解结构在编译器级别的工作原理。我认为
obj.a
或obj['a']
时会发生什么。该语法是否在运行时、编译时或JIT时转换为整数偏移量?我想我试图理解的是,它如何能够有效地将传入字符串“a”转换为整数索引,而不必执行类似于index=hash('a')%objectLength
的操作
也许我知识上的差距在于我没有完全理解结构在编译器级别的工作原理。我认为看一个过于简化的示例,看看对象在内存中的样子会有帮助:
{ a: 1, b: 2 }
// represented as
address | 0 | 1 | 2 | 3 | 4 | 5 |
value | 3 | 1 | 2 | a | b | |
对象值存储在地址0处。第一个值3指向“隐藏类”。要在0处检索对象的键“a”的值,可以读取0,然后读取存储在0中的地址的值,然后计数并查找该地址值,直到找到“a”。幸运的是,这是第一个关键点,因此我们可以返回到对象,将偏移量1添加到地址0,并找到0+1处的值。或者在伪代码(C++)中: 现在,如果我们有另一个像这样的对象:
address | 100 | 101 | 102 |
value | 3 | 5 | 7 |
然后我们可以使用与上面相同的方法,或者如果我们所处的位置是obj.a
始终获取传入的属于该隐藏类的对象,我们也可以这样做:
void* obj = 100;
assert(*obj == 3);
int value = *(obj + 1);
因此,如果引擎看到具有相同隐藏类的对象被传递给函数,它可以编译该函数,而不是使用字典中的搜索算法,它可以直接将生成的偏移量编译到函数中。但是,如果传入的对象属于不同的隐藏类,并且因此属性a可能不在同一偏移量,则这不起作用。因此,引擎需要检查对象是否属于某个隐藏类,如果不是,则返回到解释/取消优化
上面的示例确实非常简化(不是每个值都可以放在一个字节中)。(这里是V8 developer。)
JavaScript对象实际上不是隐藏的哈希映射,而是更类似于结构
为了记录在案,Bergi正确地指出,这在一个引擎中是正确的,即使在那个引擎中也不总是如此。JavaScript引擎在如何准确地在内部表示对象方面有很大的自由度,它们确实利用了这种自由度
我不明白的是语法是如何映射到固定偏移量的。即当编译器看到obj.a或obj['a']时会发生什么。该语法是否在运行时、编译时或JIT时转换为整数偏移量
该系统基于缓存和“隐藏类”(有时称为“对象形状”或“[对象]形状描述符”)
当您有一个对象obj={a:42,b:“hello”,c:null}
时,它将有一个隐藏类(我们称之为hiddenClassA
,列出所有属性及其偏移量,例如,“属性a
存储在偏移量12处”
包含属性加载(如obj.a
)的函数的第一次执行将使用未优化的代码。此代码必须检查对象,在其隐藏类的属性列表中找到a
,从中检索正确的偏移量,然后从对象中的偏移量读取以获取属性值(hidden class,offset)然后缓存用于此特定属性加载,因此如果下次出现另一个具有相同隐藏类的对象,则下一次查找(即使在仍然未优化的代码中)将运行得更快
如果函数运行得足够热,它最终会得到优化。优化编译器会查看未优化代码缓存的隐藏类和偏移量,并假设应用程序的未来行为与过去的行为相同,因此它将发出如下代码序列:
obj
是否具有隐藏类hiddenClassA
,否则取消优化这也是为什么立即编译优化代码没有意义的原因:当优化编译器没有可用的缓存类型信息(由未优化的执行生成)时,属性访问之类的事情无法合理地优化。因为这样优化编译器会问与您完全相同的问题:“我到底该如何计算偏移属性
a
映射到什么?”它不可能在“编译时”,因为在执行过程中可以添加(和删除!)属性。“JavaScript对象”-注意,您只看到一个特定的引擎。其他引擎的工作方式可能类似,或完全不同。与链接文章不同:要从属性的名称到属性存储中的实际位置,我们必须参考HiddenClass上的描述符数组,正如前面所述。-这实际上只是对描述符数组的线性搜索,它并没有那么大。然后通过内联缓存对其进行加密。@斯科特:不完全是这样,您可以将其编译为具有特定属性的特定对象结构,然后在对象结构更改时重新编译。@Jonaswillms:这将产生“编译时”的概念第一个值是3分
void* obj = 100;
assert(*obj == 3);
int value = *(obj + 1);