V8如何使用隐藏的类和内联缓存来优化代码



最近,我遇到了V8使用的隐藏类和内联缓存的概念,以优化JS代码。酷。

我知道对象在内部表示为隐藏类。两个对象可能具有相同的属性,但隐藏的类别不同(取决于分配属性的顺序(。

v8也使用内联缓存概念直接检查偏移以访问对象的属性,而不是使用对象的隐藏类确定偏移。

代码 -

function Point(x, y) {
    this.x = x;
    this.y = y;
}
function processPoint(point) {
    // console.log(point.x, point.y, point.a, point.b);
    // let x = point;
}
function main() {
    let p1 = new Point(1, 1);
    let p2 = new Point(1, 1);
    let p3 = new Point(1, 1);
    const N = 300000000;
    p1.a = 1;
    p1.b = 1;
    p2.b = 1;
    p2.a = 1;
    p3.a = 1;
    p3.b = 1;
    let start_1 = new Date();
    for(let i = 0; i< N; i++ ) {
        if (i%4 != 0) {
            processPoint(p1);
        } else {
            processPoint(p2)
        }
    }
    let end_1 = new Date();
    let t1 = (end_1 - start_1);
    let start_2 = new Date();
    for(let i = 0; i< N; i++ ) {
        if (i%4 != 0) {
            processPoint(p1);
        } else {
            processPoint(p1)
        }
    }
    let end_2 = new Date();
    let t2 = (end_2 - start_2);
    let start_3 = new Date();
    for(let i = 0; i< N; i++ ) {
        if (i%4 != 0) {
            processPoint(p1);
        } else {
            processPoint(p3)
        }
    }
    let end_3 = new Date();
    let t3 = (end_3 - start_3);
    console.log(t1, t2, t3);
}
(function(){
    main();
})();

我期望结果像 t1>(t2 = t3(因为:

第一个循环:V8将尝试在运行两次后尝试优化,但它很快会遇到不同的隐藏类,因此它将进行优化。

第二个循环:始终调用相同的对象,因此可以使用内联缓存。

第三回路:与第二个循环相同,因为隐藏类是相同的。

但结果不令人满意。我得到(类似的结果一次又一次运行( -

3553 4805 4556

问题:

  1. 为什么结果不如预期?我的假设在哪里出错?

  2. 我如何更改此代码以演示隐藏的类和内联缓存性能改进?

  3. 我是否从开始中弄错了?

  4. 通过让对象共享它们是为了记忆效率而存在的隐藏类?

  5. 还有其他一些简单的绩效改进示例?

我正在使用节点8.9.4进行测试。预先感谢。

来源:

  1. https://blog.sessionstack.com/how-javascript-works-inside-the--v8-g8-g8-g8-pips-5-tips-on-how-write-write-write-optimatire-code-ac089e6e62b12e

  2. https://draft.li/blog/2016/12/22/javascript-engines-engines-hiddend-classes/

  3. https://richardartoul.github.io/jekyll/jekyll/update/2015/04/26/hiddend-classes.html

以及更多..

v8开发人员在这里。摘要是: Microbenchmarking很难,不要这样做。

首先,使用您的代码发布的代码,我将380 380 380视为输出,这是可以预期的,因为function processPoint是空的,因此所有循环都执行相同的工作(即无效(,无论您选择哪个点对象。

测量单态和2向多态性内联粘贴之间的性能差异很困难,因为它不是很大,因此您必须非常谨慎地对基准还做什么。例如,console.log是如此慢,以至于它会遮蔽其他所有内容。

您还必须谨慎对待内联的影响。当您的基准测试有许多迭代时,代码将得到优化(运行WAAAAY两次以上(,并且优化编译器(在某种程度上(内联函数将允许后续优化(具体来说:消除各种事情(,从而可以大大进行。更改您的测量内容。编写有意义的微型基准很难;您将不会检查生成的组装和/或了解您正在调查的JavaScript引擎的实现细节。

要记住的另一件事是内联缓存的位置,以及随着时间的流逝,它们将拥有什么状态。忽略内衬,像processPoint这样的函数不知道或关心它的何处。一旦其内联缓存是多态性的,即使以后在您的基准测试中(在这种情况下,在第二个和第三个环中(,它们将保持多态性。

试图隔离效果时要记住的另一件事是,长期运行的功能将在其运行时被编译在后台,然后在堆栈上替换某个时候(" OSR"(,这会添加各种噪音为您的测量结果。当您以不同的循环长度进行热身调用它们时,它们仍然会在后台进行编译,但是无法可靠地等待该背景工作。您可以诉诸于用于开发的命令行旗帜,但是您将不再衡量常规行为。

无论如何,以下是尝试制作类似于您的测试的尝试,该测试产生了合理的结果(关于我的机器上的100 180 280(:

function Point() {}
// These three functions are identical, but they will be called with different
// inputs and hence collect different type feedback:
function processPointMonomorphic(N, point) {
  let sum = 0;
  for (let i = 0; i < N; i++) {
    sum += point.a;
  }
  return sum;
}
function processPointPolymorphic(N, point) {
  let sum = 0;
  for (let i = 0; i < N; i++) {
    sum += point.a;
  }
  return sum;
}
function processPointGeneric(N, point) {
  let sum = 0;
  for (let i = 0; i < N; i++) {
    sum += point.a;
  }
  return sum;
}
let p1 = new Point();
let p2 = new Point();
let p3 = new Point();
let p4 = new Point();
const warmup = 12000;
const N = 100000000;
let sum = 0;
p1.a = 1;
p2.b = 1;
p2.a = 1;
p3.c = 1;
p3.b = 1;
p3.a = 1;
p4.d = 1;
p4.c = 1;
p4.b = 1;
p4.a = 1;
processPointMonomorphic(warmup, p1);
processPointMonomorphic(1, p1);
let start_1 = Date.now();
sum += processPointMonomorphic(N, p1);
let t1 = Date.now() - start_1;
processPointPolymorphic(2, p1);
processPointPolymorphic(2, p2);
processPointPolymorphic(2, p3);
processPointPolymorphic(warmup, p4);
processPointPolymorphic(1, p4);
let start_2 = Date.now();
sum += processPointPolymorphic(N, p1);
let t2 = Date.now() - start_2;
processPointGeneric(warmup, 1);
processPointGeneric(1, 1);
let start_3 = Date.now();
sum += processPointGeneric(N, p1);
let t3 = Date.now() - start_3;
console.log(t1, t2, t3);

最新更新