为什么JavaScript中存在单态和多态性问题



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

(..)原因是,它必须以动态的方式编写,所以它可以检查每个组件,无论其模型结构如何喜欢虚拟机不喜欢这种动态代码,因为它们不能优化它。它被认为是多态的,因为对象的形状并不总是一样的。Angular在每个组件的运行时,它们是单态的,因为它们知道组件模型的形状。虚拟机可以完美地优化此代码,使其执行速度非常快。好的问题是我们不必太在意,因为Angular会自动执行。(..)

现在,我试图找到单体与多态的例子,但在任何地方都找不到。有人能解释一下区别吗?为什么它更快?

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

这一原因在本v8特定指南文档中有充分的说明,其中解释了整数与一般数的优化。假设你有:

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

如果你总是用整数调用这个函数,这个方法可以通过编译一个在CPU上进行整数求和的函数来优化,这很快。如果在优化后给它一个非整数值,那么VM会取消对函数的优化,并返回到未优化的版本,因为它无法对非整数执行整数求和,并且函数会返回错误的结果。

在指定重载单态方法的语言中,只需编译具有不同参数签名的同一方法名称的多个版本,然后对其进行单独优化,就可以解决这个问题。这意味着您调用不同的优化方法,因为使用不同类型的参数需要使用不同的重载方法,因此不存在使用哪种方法的问题。

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

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

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

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

据我所知,单态性是一个非常罕见的术语。我个人从未听说过它被用于编码。然而,为了弄清楚什么是单态性,我认为我们可以通过观察多态性来推断它的含义

多态性:是指许多(多)不同的对象可以用相同的类型表示给机器/运行时/解释器。例如,在C#中,可以有任意多个实现ICloneable的类,其中任何一个都可以在通用链表的复制构造函数中使用(例如)。如果你对感兴趣,这里以完整的未测试类为例

那么,单形是什么意思呢?

对我来说,单形意味着对象的解释器处理它所期望的EXACT类型,并且不可能继承或修改所期望的类型。在这种情况下,使用鸭子类型的javascript语言,VM会说"这个javascript对象具有这些exact属性,这些属性属于这些exact型,并像它们一样命名为"。在C#中,如果我们想是单态的,那么泛型类型限制是不可能的,因为T必须一直是同一类型。

此链接提供了一个很好的指南,说明为什么这对性能很重要。对我来说,这可以总结如下。

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

事实上,引擎试图处理传递给同一函数的少量不同对象,但如果对象在同一行代码中看起来总是一样,那么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)

您提供的链接引用的要点是,AngularJS开箱即用提供的代码使其所有javascript函数参数都是"单态的",因为每次调用传递到其中的对象时,它们恰好具有相同的结构。

最新更新