为什么单态和多态在 JavaScript 中很重要?

2022-01-24 00:00:00 polymorphism javascript monomorphism

我一直在阅读一些关于变化检测的文章,他们都说单态函数比多态函数快得多.例如,这是一个引用:

I've been reading some articles on change detection, and all of them say that monomorphic functions are much faster than polymorphic. For example, here is a quote:

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

(..)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.(..)

来源

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

Now, I was trying to find examples of monomoprhic vs polymorphic, but couldn't find it anywhere. Could anyone care to explain the difference, and why is it faster?

推荐答案

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

The answer lies in the fact that VMs can do heuristic detection of "hot functions", meaning code that is executed hundreds or even thousands of times. If a function's execution count exceeds a predetermined limit, the VMs optimizer might pick up that bit of code and attempt to compile an optimized version based on the arguments passed to the function. In this case, it presumes your function will always be called with the same type of arguments (not necessarily the same objects).

原因在这个 v8-具体指南文档解释了整数与一般数字优化.假设你有:

The reason for this is well-documented in this v8-specific guideline document where an integer vs. general number optimization is explained. Say you have:

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

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

...and you're always calling this function with integers, this method might be optimized by compiling a function that does integer summation on the CPU, which is fast. If after optimization you feed it a non-integer value, then the VM deoptimizes the function and falls back to the unoptimized version, since it cannot perform integer summation on non-integers and the function would return erroneous results.

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

In languages where you specify overloaded monomorphic methods you can get around this problem by simply compiling multiple versions of the same method name with different argument signatures which are then optimized on their own. This means that you call different optimized methods because using differenty typed arguments requires you to use a different overloaded method, so there's no question of which method you're using.

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

You might think that you could keep multiple copies of optimized functions in the VM and check types to determine which optimized compiled function to use. In theory, that would work, if type checking before method invocation were free or very inexpensive. In practice, that usually doesn't turn out to be the case, and you'd probably want to balance things against real-world code to determine the best tradeoff threshold.

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

Here's a more generalized explanation v8's optimizing compiler in particular (from Google I/O 2012):

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

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

相关文章