V8引擎和JavaScript优化技巧
V8 是 Google 用于编译 JavaScript 的引擎。 Firefox 有自己的引擎 SpiderMonkey,它与 V8 非常相似,但也有区别。 我们将在本文中讨论 V8 引擎。
关于 V8 发动机的几个事实:
- 用 C++ 编写并用于 Chrome 和 Node.js(以及 最新版本的 Microsoft Edge )
- 实现 ECMA-262 中指定的 ECMAScript
JavaScript 之旅
那么,当我们将 JavaScript 发送给 V8 引擎解析时(这是在它被缩小、丑化和你对 JavaScript 代码做的任何其他疯狂的事情之后)究竟会发生什么?
我创建了下图,显示了所有步骤,然后我们将详细讨论每个步骤:
在本文中,我们将讨论如何解析 JavaScript 代码以及如何将尽可能多的 JavaScript 获取到优化编译器。 优化编译器(又名 Turbofan)获取我们的 JavaScript 代码并将其转换为高性能机器代码,因此我们可以提供的代码越多,我们的应用程序就越快。 附带说明一下,Chrome 中的解释器称为 Ignition.
解析 JavaScript
所以我们的 JavaScript 代码的第一个处理就是解析它。 让我们来讨论一下什么是解析。
解析有两个阶段:
- Eager (full-parse) - 这会立即解析每一行
- Lazy (pre-parse)- 做最少的事情,解析我们需要的东西,剩下的留到以后
哪个更好? 这一切都取决于。
让我们看一些代码。
// eager parse declarations right away const a = 1; const b = 2; // lazily parse this as we don't need it right away function add(a, b) { return a + b; } // oh looks like we do need add so lets go back and parse it add(a, b);
所以这里我们的变量声明将是 eager parsed
但我们的函数是 lazily parsed
。 这很好,直到我们得到 add(a, b)
因为我们需要我们的 add
函数,所以它会更快 eager parse
add
马上。
要立即使用 eager parse
add
函数,我们可以这样做:
// eager parse declarations right away const a = 1; const b = 2; // eager parse this too var add = (function(a, b) { return a + b; })(); // we can use this right away as we have eager parsed // already add(a, b);
这就是您使用的大多数模块的创建方式。
函数内联
Chrome 有时会从根本上重写您的 JavaScript,其中一个示例是内联正在使用的函数。
我们以下面的代码为例:
const square = (x) => { return x * x } const callFunction100Times = (func) => { for(let i = 0; i < 100; i++) { // the func param will be called 100 times func(2) } } callFunction100Times(square)
上述代码将被V8引擎优化如下:
const square = (x) => { return x * x } const callFunction100Times = (func) => { for(let i = 100; i < 100; i++) { // the function is inlined so we don't have // to keep calling func return x * x } } callFunction100Times(square)
从上面可以看出,V8 本质上是删除了我们调用 func
的步骤,而是内联了 square
的主体。 这非常有用,因为它将提高我们代码的性能。
函数内联陷阱
这种方法有一个小问题,让我们看下面的代码示例:
const square = (x) => { return x * x } const cube = (x) => { return x * x * x } const callFunction100Times = (func) => { for(let i = 100; i < 100; i++) { // the function is inlined so we don't have // to keep calling func func(2) } } callFunction100Times(square) callFunction100Times(cube)
所以这一次我们调用了square
函数100
次之后,我们将调用cube
函数100
次。 在调用 cube
之前,我们必须首先对 callFunction100Times
进行反优化,因为我们已经内联了 square
函数体。 在这种情况下,square
函数似乎比 cube
函数更快,但实际情况是反优化步骤使执行时间更长。
对象
说到对象,V8 底层有一个类型系统来区分你的对象:
单态
对象具有相同的键,没有区别。
// mono example const person = { name: 'John' } const person2 = { name: 'Paul' }
多态性
这些对象具有相似的结构,但有一些小的差异。
// poly example const person = { name: 'John' } const person2 = { name: 'Paul', age: 27 }
巨变
对象完全不同,无法比较。
// mega example const person = { name: 'John' } const building = { rooms: ['cafe', 'meeting room A', 'meeting room B'], doors: 27 }
所以现在我们知道了 V8 中的不同对象,让我们看看 V8 是如何优化我们的对象的。
隐藏类
隐藏类是 V8 识别我们的对象的方式。
让我们把它分解成几个步骤。
我们声明一个对象:
const obj = { name: 'John'}
然后 V8 将为这个对象声明一个 classId
。
const objClassId = ['name', 1]
然后我们的对象创建如下:
const obj = {...objClassId, 'John'}
然后当我们像这样访问对象的 name
属性时:
obj.name
V8 执行以下查找:
obj[getProp(obj[0], name)]
这是 V8 在创建对象时所经历的过程,现在让我们看看如何优化对象并重用 classIds
。
创建对象的技巧
如果可以,您应该 在构造函数 中声明您的属性。 这将确保对象结构保持不变,以便 V8 可以优化您的对象。
class Point { constructor(x,y) { this.x = x this.y = y } } const p1 = new Point(11, 22) // hidden classId created const p2 = new Point(33, 44)
你应该保持属性顺序不变,举个例子:
const obj = { a: 1 } // hidden class created obj.b = 3 const obj2 = { b: 3 } // another hidden class created obj2.a = 1 // this would be better const obj = { a: 1 } // hidden class created obj.b = 3 const obj2 = { a: 1 } // hidden class is reused obj2.b = 3
一般优化技巧
因此,现在让我们了解一些有助于更好地优化 JavaScript 代码的一般技巧。
修复函数参数类型
当参数被传递给函数时,它们是相同的类型很重要。 如果参数类型不同,Turbofan 将在 4 次尝试后放弃尝试优化您的 JavaScript。
举个例子:
function add(x,y) { return x + y } add(1,2) // monomorphic add('a', 'b') // polymorphic add(true, false) add({},{}) add([],[]) // megamorphic - at this stage, 4+ tries, no optimization will happen
另一个提示是确保 在全局范围 中声明类:
// don't do this function createPoint(x, y) { class Point { constructor(x,y) { this.x = x this.y = y } } // new point object created every time return new Point(x,y) } function length(point) { //... }
结论
所以我希望你了解了一些关于 V8 如何在底层工作以及如何编写更好优化的 JavaScript 代码的知识。