为什么 JavaScript 中的单态和多态物质?

Why do monomorphic and polymorphic matter in JavaScript?

我一直在阅读一些关于变化检测的文章,他们都说单态函数比多态函数快得多。例如,这里有一段话:

(..)The reason for that is, that it has to be written in a dynamic way, so it can check every component no matter what its model structure looks like. VMs don’t like this sort of dynamic code, because they can’t optimize it. It’s considered polymorphic as the shape of the objects isn’t always the same. Angular creates change detector classes at runtime for each component, which are monomorphic, because they know exactly what the shape of the component’s model is. VMs can perfectly optimize this code, which makes it very fast to execute. The good thing is that we don’t have to care about that too much, because Angular does it automatically.(..)

Source

现在,我试图找到单态与多态的示例,但找不到任何地方。谁能解释一下区别,为什么更快?

答案在于虚拟机可以对 "hot functions" 进行启发式检测,即执行数百甚至数千次的代码。如果函数的执行次数超过预定限制,VM 优化器可能会选择那部分代码并尝试根据传递给函数的参数编译优化版本。在这种情况下,它假定您的函数将始终使用相同的 type 参数调用(不一定是 same objects)。

其原因在 v8-specific guideline document 中有详细记录,其中解释了整数与一般数字优化。假设您有:

function add(a, b) { return a + b; }

...并且您总是用整数调用此函数,可以通过编译一个函数来优化此方法,该函数在 CPU 上进行整数求和,速度很快。如果在优化后你给它一个非整数值,然后 VM 取消优化函数并回退到未优化的版本,因为它不能对非整数执行整数求和并且函数会 return 错误的结果。

在指定重载单态方法的语言中,您可以通过简单地编译具有不同参数签名的相同方法名称的多个版本来解决这个问题,然后自行优化。这意味着您调用不同的优化方法,因为使用不同类型的参数需要您使用不同的重载方法,所以您使用的是哪种方法是毫无疑问的。

您可能认为可以在 VM 中保留优化函数的多个副本并检查类型以确定使用哪个优化编译函数。从理论上讲,如果方法调用之前的类型检查是免费的或非常便宜的,那将是可行的。在实践中,通常情况并非如此,您可能希望根据实际代码进行平衡以确定最佳权衡阈值。

这里有一个更笼统的解释,特别是 v8 的优化编译器(来自 Google I/O 2012):

https://youtu.be/UJPdhx5zTaw?t=26m26s

简而言之:在 JIT 编译器中优化了以相同类型反复调用的函数,因此速度更快。

据我所知,单态是一个非常不常见的术语。我个人从未听说过它用于编码。不过,要弄清楚单态是什么,我认为我们可以通过查看多态是什么来推断它的含义。

多态性:许多(多)不同的对象可以用相同的类型表示到 machine/runtime/interpreter。例如,在 C# 中,您可以拥有任意数量的 类 实现 ICloneable 并且它们中的任何一个都可以用于通用 linked 列表的复制构造函数(例如). Full un-tested class here as an example if you're interested

好的,单态是什么意思?

单态对我来说意味着对象的解释器处理它所期望的确切类型,并且不可能继承或修改期望的类型。在这种情况下,使用鸭子类型 javascript 语言,VM 表示 "this javascript object has these exact properties which are of these exact types and named exactly like they are"。在 C# 中,如果我们想成为单态的,那么泛型类型限制是不可能的,因为 T 必须始终是相同的类型。

This link 很好地说明了为什么这对性能很重要。对我来说,这可以总结如下。

Javascript 引擎希望避免 table 查找属性,而是执行对象指针偏移。使用单态性,给定代码行中对象的对象偏移量将始终相同,并且 VM 可以很容易地弄清楚如何使用指针添加而不是 table 查找来执行查找。

实际上,引擎会尝试处理传递给同一函数的少量不同对象,但如果对象在同一行代码中看起来始终相同,则 VM 将是最快的。

为清楚起见的示例

以下示例有效 javascript,但函数 f1 的参数 o 不是单态的,因为 VM 需要处理传入的两个不同形状的对象。

function f1(o) {
  console.log(o.prop1)
  console.log(o.prop2)
}

// ...

o1 = { prop1: 'prop1', prop2: 'prop2' }
o2 = { prop1: 'prop1', prop2: 'prop2', prop3: 'prop3' }

f1(o1)
f1(o2)

您提供的 link 引述的要点是,开箱即用的 AngularJS 提供了使所有 javascript 函数参数成为 "monomorphic" 因为传给它们的对象恰好在每次调用时都具有相同的结构。