为V8优化JavaScript

V8可以让JavaScript加速350倍,所以我们有很多优化的空间,在这之前,我们必须了解V8优化JavaScript的原理,然后写出针对V8的代码。

下面会使用"Be prepared"这个词语,单词的意思是:

  • 理解V8优化原理
  • 写出深思熟虑的JavaScript代码
  • 使用工具测试性能,帮助改进

隐藏类

变量类型对生成高速优化的代码非常有帮助,但是JavaScript确实若类型的。如何才能让JavaScript跑的像C++一样快呢?答案就hidden classes

Hidden Classes让JavaScript更快

  • V8在内部为创建隐藏类
  • 具有相同隐藏类的对象可以使用相同的优化代码

如果不理解上面的原理,可以查看V8设计原理或者ppt

tips:使用构造函数初始化数据

function Point(x, y) {
 this.x = x;
 this.y = y;
}
var p1 = new Point(11, 22);
var p2 = new Point(33, 44);
p2.z = 55// warning! p1 and p2 now have// different hidden classes

当V8解析p2.z = 55时,p1,p2使用了不同的隐藏类了,就意味着要创建一个新的隐藏类,cache也要重建,所以尽量不要这样。如果你没有用构造函数,请保证对象赋属性的顺序是一样的。

高效的描述值

Be Prepared - Numbers

我们看到下图中,V8使用一个标签来表示不同的对象,很明显对于数字,我们使用能用31位有符号整数是效率最高的。


为V8优化JavaScript_第1张图片
Tagged Values

Prefer numeric values that can be represented as 31-bit signed integers

var i = 42; // this is a 31-bit signed integer
var j = 4.2; // this is a double-precision floating point number

Be Prepared - Arrays

V8有两种处理数组的方式

  • Fast Elements: 线性存贮,连续的buffer,性能好
  • Dictionary Elements: hash table storage otherwise

避免性能陷阱

  • 使用从0开始连续的key
    下面明显是字典形式,性能不如Fast Elements模式
var person = [];
person["firstName"] = "John";
person["lastName"] = "Doe";
person["age"] = 46;
var x = person.length;         // person.length will return 0
var y = person[0];             // person[0] will return undefined
  • 不要预先建立太大的数组(e.g. > 64K elements),JavaScript不需要像C语言指定数组大小
    这样会变成稀疏数组,也会使用字典模式创建


    为V8优化JavaScript_第2张图片
    不同的数组模式
  • 不要删除数据,特别是数值型的数组
    这样会导致两种模式的切换,都会很费时

  • 不要使用未初始化的数组,或者被删除的元素

a = new Array();
for (var b = 0; b < 10; b++) {
  a[0] |= b;  // Oh no! 这里a[0]是undefined,v8会做转换,结果是对的,但是更费时间
}
//vs.
a = new Array();
a[0] = 0;
for (var b = 0; b < 10; b++) {
  a[0] |= b;  // Much better! 2x faster.
}
  • 不要导致数组boxing and unboxing
    看看下图,既有double,又有其他类型,下面会导致hidden class的两次转变和三次申请空间。如果不理解可以看视频。


    为V8优化JavaScript_第3张图片
    Paste_Image.png
var a = [77, 88, 0.5, true]; 这样让解析器一次知道所有信息会更好

总结一下使用数组需要注意的地方:

  1. 使用[]初始化数组
  2. 小数组可以先指定大小 (<64k) ,因为会使用快速模式


    为V8优化JavaScript_第4张图片
    小数组

    为V8优化JavaScript_第5张图片
    指定大小
  3. 不要在数字数组里面使用非数字,数值型的性能已经优化过,包括double
  4. 如果没有使用数组字面量初始化数组,注意不必要的转换发生

Be Prepared - Full Compiler

V8有两个编译器。你没听错,是编译器,JavaScript是动态语言,一般的动态语言都由解析器解析执行,但是V8可以直接编译成可执行代码。

  • "Full" compiler 为所有JavaScript生成可执行代码
  • Optimizing compiler 为大多数JavaScript代码生成更优化的代码

"Full" Compiler立刻运行代码

  • 生成好的但不是最好的JIT代码,但是支持所有的JavaScript功能
  • 编译期间并不假定类型信息,并期待类型在运行时变化
  • 运行的时候获取类型并使用Inline Caches (or ICs)去加速执行
    为V8优化JavaScript_第6张图片
    Paste_Image.png

Full Compiler Example

this.isPrimeDivisible = function(candidate) {
  for (var i = 1; i <= this.prime_count; ++i) {
    if (candidate % this.primes[i] == 0) return true;
  }
  return false;
}

candidate % this.primes[i]会编译成:

为V8优化JavaScript_第7张图片
生成的汇编代码使用了IC

为V8优化JavaScript_第8张图片
生成的汇编代码使用了IC

IC的目的是加速处理类型信息,它为JavaScript操作存贮类型相关的代码,当代码运行的时候,它验证所假定的类型信息,然后使用IC去处理。所以,能处理多种类型的操作性能要查一些。

优化tips:

单一的操作比多样的操作好
Monomorphic use of operations is preferred over polymorphic operations

function add(x, y) {
  return x + y;
}

add(1, 2);      // + in add is monomorphic(所有的操作都是数值类型的话)
add("a", "b");  // + in add becomes polymorphic

Optimizing compiler

优化编译实际上使用inline技术,还记得在C++中的inline吗,是一个意思。短小的函数,并且经常调用的函数,会被编译器优化成inline。一般单一类型的函数和构造函数会被inline。

我们看一下代码(**inline可以避免跳转
**):


为V8优化JavaScript_第9张图片
inline会避免跳转

为V8优化JavaScript_第10张图片
深度优化后更快了

一些有用的命令

d8 --trace-opt primes.js //log names of optimized functions to stdout
d8 --trace-bailout primes.js //找到被try catch包住不能优化的函数
d8 --trace-deopt primes.js //v8必须取消优化的代码,找到以后可以修改
给chrome加启动参数
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \--js-flags="--trace-opt --trace-deopt --trace-bailout"

prime.js是一个测试获得质数的函数,ppt的作者用来测试性能用的。

function Primes() {
  this.prime_count = 0;
  this.primes = new Array(25000);
  this.getPrimeCount = function() { return this.prime_count; }
  this.getPrime = function(i) { return this.primes[i]; }
  this.addPrime = function(i) {
    this.primes[this.prime_count++] = i;
  }

  this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
      if ((candidate % this.primes[i]) == 0) return true;
    }
    return false;
  }
};

function main() {
  p = new Primes();
  var c = 1;
  while (p.getPrimeCount() < 25000) {
    if (!p.isPrimeDivisible(c)) {
      p.addPrime(c);
    }
    c++;
  }
  print(p.getPrime(p.getPrimeCount()-1));
}

main();

你需要编译v8,获得d8命令行,在windows在编译可以参考这篇文章使用visual studio编译v8

d8

这些命令跑出来的结果还看不太懂,等以后仔细研究在来分享
为V8优化JavaScript_第11张图片
命令结果案例

本文翻译自这个ppt,可以观看youtube演讲
这篇文章也很好Performance Tips for JavaScript in V8

你可能感兴趣的:(为V8优化JavaScript)