V8有了全新的超快速非优化JS编译器,性能提高5-15%( 四 )


你可能会注意到 , 我们现在在堆栈框架上有一个未使用的插槽 , 字节码偏移量就会在这个插槽上 。 由于我们希望保持堆栈的其余部分不变 , 因此我们不能放弃它 。 我们重新调整了这个堆栈插槽的功能 , 让它为当前正在执行的函数缓存“反馈向量” 。 这是用于存储对象形态数据的向量 , 大多数操作都需要加载它 。 我们要做的只是谨慎一点对待 OSR , 确保我们为这个插槽要么换入正确的字节码偏移量 , 要么换入正确的反馈向量 。
于是 Sparkplug 堆栈框架为:
V8有了全新的超快速非优化JS编译器,性能提高5-15%
本文插图
一个 V8 Sparkplug 堆栈框架
交给内置代码
实际上 , Sparkplug 很少生成自己的代码 。 JavaScript 语义很复杂 , 即使执行最简单的操作也需要大量代码 。 由于多种原因 , 强制 Sparkplug 在每次编译时内联重新生成这些代码都是不好的:
由于需要生成大量代码 , 这将明显增加编译时间 ,
这会增加 Sparkplug 代码的内存消耗 , 并且
我们必须重新实现用于 Sparkplug 的一堆 JavaScript 功能的代码源 , 这可能意味着会有更多的错误和更大的受攻击面 。
因此 , 大多数 Sparkplug 代码只是调用“内置代码” , 即嵌入二进制文件中的小段机器码片段 , 以完成那些脏活儿 。 这些内置代码要么就是解析器用的那些 , 或者至少与解析器的字节码处理程序共享大部分代码 。
实际上 , Sparkplug 代码基本上只是内置代码的调用和控制流:
你现在可能会想 , “那么 , 这一切到底有什么意义?Sparkplug 不是在做与解析器相同的工作吗?”——你的疑问是有道理的 。 在许多方面 , Sparkplug 只是解析器执行的一个序列化 , 它调用相同的内置函数并维护相同的堆栈框架 。 但这样做也是值得的 , 因为它消除(或更准确地说是预编译)了那些不可移动的解析器开销 , 例如操作数解码和下一个字节码分派 。
事实证明 , 解析器破坏了许多 CPU 优化工作:解析器从内存中动态读取静态操作数 , 从而迫使 CPU 停顿或推测值可能是多少 。 分派到下一个字节码需要成功的分支预测才能保持高性能 , 即使推测和预测正确 , 你还是要执行所有解码和分派代码 , 并且你还是会在各个缓冲区和缓存中浪费宝贵的空间 。 CPU 实际上本身就是一个解析器 , 只不过它是机器码的解析器 。 这样看来 , Sparkplug 是从 Ignition 字节码到 CPU 字节码的一个“转译器” , 将你的函数从在“仿真器”中运行移到了“原生”运行 。
性能表现
那么 , Sparkplug 在现实场景中的性能表现如何呢?我们用 Chrome M91 跑了一些基准测试 , 用了几个性能 bot , 分别启用和关闭 Sparkplug 来观察其影响 。