为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位有符号整数是效率最高的。


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语言指定数组大小
    这样会变成稀疏数组,也会使用字典模式创建


    不同的数组模式
  • 不要删除数据,特别是数值型的数组
    这样会导致两种模式的切换,都会很费时

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

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的两次转变和三次申请空间。如果不理解可以看视频。


    Paste_Image.png
var a = [77, 88, 0.5, true]; 这样让解析器一次知道所有信息会更好

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

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


    小数组

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

Be Prepared - Full Compiler

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

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

"Full" Compiler立刻运行代码

  • 生成好的但不是最好的JIT代码,但是支持所有的JavaScript功能
  • 编译期间并不假定类型信息,并期待类型在运行时变化
  • 运行的时候获取类型并使用Inline Caches (or ICs)去加速执行
    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]会编译成:

生成的汇编代码使用了IC

生成的汇编代码使用了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可以避免跳转
**):


inline会避免跳转

深度优化后更快了

一些有用的命令

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

这些命令跑出来的结果还看不太懂,等以后仔细研究在来分享
命令结果案例

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容