面向前端开发者的V8性能优化

二叶草 2020年2月3日10:50:52优化评论阅读模式

V8是一个由丹麦Google使用C++开发的开源JavaScript引擎,用于Google Chrome中,目前该JavaScript引擎已用于其它项目的开发。

 

在V8中的数字表示

 

在V8中数字有小整数(SMI)和引用类型,它们是通过标记位进行表示的,以提升性能。

 

例如把整数42编码为0×0000002a00000000;指针0×12345678编码为0×12345679,非整数数值存放在堆里。

 

这个例子是为了说明基于标记位的存储方式,在 V8 引擎的内部并不是这么存储的。

 

面向前端开发者的V8性能优化

 

在V8代码中使用C++的位运算去做比较,是为了提升V8引擎本身的性能。

 

面向前端开发者的V8性能优化

如图我做了一个基准测试。左边的代码是V8单元测试中的代码,可见在32位中使用的是i30,在64位系统上,V8则会使用i31。

 

性能测试是基于64位进行的,通过性能测试会发现,前面的速度都非常快,到了i31再往上加的时候,速度就成倍的下降。

 

面向前端开发者的V8性能优化

这套代码是js的另一段单元测试,测试的是js的代码。当我们不知道一个API如何使用或不知道一个东西内部是怎样的时候,去看它的单元测试,就很容易知道它外部表现出来的是什么样,我们该如何去用。

 

面向前端开发者的V8性能优化

在V8的class里,它们都继承了一个Value。Value分为Object和Primitive。Primitive下面是number,在number下面又会分成Int32和Uint32。

 

Object下面的分类很多,比如数组函数,这些基本上都是Object类型。

 

Javascript中的“加法”

 

分析完数据类型,再来看看它的运算。在运算中经常会遇到一些问题,例如:

 

为什么++[[]][+[]]+[+[]]=10?

 

{}+{}等于多少?

 

为什么[1,2]+[3,4]不等于[1,2,3,4]?

 

在js的加法运算中,它有自己类型转换的规则。js是一种弱类型,如果用不同类型去做加法,它会直接编译器报错。弱类型不是因为它没有类型,只是它不像静态语言那样进行强制性转换,而是有默认的规则进行转换。

 

面向前端开发者的V8性能优化

 

V8的算数运算

 

V8算数运算的快速模式就是直接调用二进制代码assembly,包括小整数、堆区的数值,还有一些怪异的类型undefined、null、true、false,以及字符串。

 

对象运算使用C++实现比较慢。

 

快速模式

 

编译一段代码a + b,先把a放到一个寄存器,再把b放到一个寄存器,然后调一个函数,这个函数可以将a和b相加,相加结果会放到内存里。这是常规的编译方法。

 

要让编译的速度变快,进行优化编译。把a和b放入寄存器,直接调用CPU指令add,然后将两个寄存器相加,结果放进eax。但假如a和b是字符串,就不能直接进行优化编译。

 

Type feedback

 

V8引入了类型反馈技术。当我们进行二元运算的时候,V8会对所有运算的参数进行类型反馈,类型反馈给V8引擎。

 

面向前端开发者的V8性能优化

 

这就是V8使用的优化编辑器。使用类型反馈做动态检查,一般而言会在编译阶段提前检查。检查之后,使用该类型作为动态类型。如果检查失败,去优化(deopt)。去优化之后,可能会使用解释器运行中间码。

 

面向前端开发者的V8性能优化

面向前端开发者的V8性能优化

 

 

   去优化Deoptimization    

 

去优化就是生成一个未优化的帧,运算时,V8会把优化的帧去掉,调用的时候V8再重新进行优化。

 

当去优化并再次优化完成之后,最终会生成重新优化过的机器码。如果要进行别的操作,V8还会进行优化操作。

 

 

    要避免“去优化”    

 

去优化的消耗大,主要是因为重新优化的消耗非常大。

 

如果我们不恰当的使用类型反馈信息,那么我们就会陷入去优化的怪圈:函数不停地去优化,然后再重新优化,直到我们达到了重优化的次数限制,这时我们的函数将再也不会被V8引擎优化。

 

 

  Can we do better?  

 

现在在很多库里,它们直接使用位运算和asm.js。

 

面向前端开发者的V8性能优化

在位运算中,只对低32位有效。这是一个非常重要的类型反馈信息。

 

 截断 

 

 

在( x + y )|0运算时,我们只关心低32位的结果。即使x,y都是int52,我们也只关心x和y的低32位。

 

表达式+a[i] 不区分a[i]=undefined和a[i]=NaN。在稀疏数组中,我们会读取到NaN!而不是undefined。

 

表达式c ? x : y也不需要区分c=1和c=true。

 

“截断”的其它用途

 

截断还可以用于其他优化:

 

从double到integer转换时的负零检查;

 

乘法运算的负零检查;

 

读取数组元素时的undefined检查;

 

使引擎能更精准地表示类型。

 

截断传播只在V8的Turbofan编译器有效。

 

面临的挑战

 

目前,引擎首先进行截断分析,而类型反馈不影响截断。

 

例如,( x + y|0 )中x和y将会被作为整型。理想情况下,使用x和y的类型反馈,然后进行int32加法。然而,很多情况下,最明智的选择往往是“更差”的表示法。比如a + b + 0.5应该是float64,即使a,b被反馈为整型。

 

未来方向

 

JavaScript可以使用任意的精确的整数。我们可以更加精准的控制V8引擎生成的代码,也许以后会有(U)Int64或BigNum类型。

 

在未来的方向上有TypedArray、WebAssembly和SIMD三种。

 

TypedArray目前已经有了并被很多引擎和浏览器实现。

 

WebAssembly:我们可以用C++写js代码,写完直接生成抽象语法树,让V8进行进一步编译。

 

SIMD充分发挥了CPU的优势,单指令多运算并行。

 

V8 Binding:JS Object和DOM对象   

面向前端开发者的V8性能优化

如上图,右边是DOM树。div下面有一个段落,段落有两个子元素。右边与之对应的,div会生成一个div的js object,p会生成p object。

 

TurboFan

 

面向前端开发者的V8性能优化

TurboFan是V8即将使用的新引擎。

 

面向前端开发者的V8性能优化

TurboFan IR是一个内部表示。当我们写了一串代码,V8引擎对代码进行内部表示,最终才会进行优化操作,翻译成我们所需要的代码。TurboFan所有的表示、优化都是基于图。

 

面向前端开发者的V8性能优化

V8速度快的原因就是内部使用了Hidden Classes,能直接把代码编译成机器码,性能非常高。

 

   整数相加   

 

首先我们创建一个add,传了一个对象,依靠对象的两个属性(其实是一个属性)进行相加。一个属性表示它的类型相同。然后进行循环、相加。

 

面向前端开发者的V8性能优化

我们用d8分析它的性能,如果没有 d8 我们可以使用 ndoe.js 代替。图上第一行进行了优化,并且写了原因small function。因为函数非常小,V8对它进行了内联操作。

 

混合相加

 

 

面向前端开发者的V8性能优化

混合相加和整数相加的区别就是在于,我们生成0-1的随机数,用0.5进行判断。

 

面向前端开发者的V8性能优化

最后几行显示,本来想优化,最后发现不能优化,因为没有足够的类型信息。

 

面向前端开发者的V8性能优化

图中最长的一行代码经过优化后,下面的代码又不能优化了,要想继续优化还要等待类型信息。

 

--trace-depot

 

 

面向前端开发者的V8性能优化

 

面向前端开发者的V8性能优化

V8内部进行了去优化。

 

full gc

 

 

面向前端开发者的V8性能优化

上面的代码不变,下面的代码在数组里放了一个很大的对象,有5%的概率将这个对象释放。

 

--allowe-natives-syntax

 

 

面向前端开发者的V8性能优化

当我们在调试js性能或写一些性能要求很高的库的时候,会经常使用到这个语法。它允许我们在js代码里使用C++函数。

 

面向前端开发者的V8性能优化

这是代码生效后的结果。

 

Bluebird promise

 

面向前端开发者的V8性能优化

Bluebird是用在promise的一个库,这是我经常使用的一个库。在很多场景下它比原生的用得还要高,因为它能加快object的访问速度。

 

累加

 

 

面向前端开发者的V8性能优化

我们进行一个累加的递归。比如sum操作,如果是1返回1,不是1则返回当前数和之前数的累加。

 

下图是它的调用过程。

 

面向前端开发者的V8性能优化

 

调用栈

 

每次调用函数要开辟一个栈,当再调用的时候,从这个函数里又开辟出了一个新的栈然后返回。最后返回我们的值。

 

面向前端开发者的V8性能优化

如果我们使用的是尾调用(函数的最末尾调用了另一个函数),其实我们不用开辟新的栈,只需要使用同一个栈去做所有的操作都行。因为即使开辟了新栈,当前栈也不再使用了。这对内存的保护有很大的优化作用。

 

面向前端开发者的V8性能优化

面向前端开发者的V8性能优化

如图可见,优化后中间有一个栈的调用丢失了。

 

现在的解决方式就是我们可以进行显式指定,有三个待选的语法return continue、!return、#function()。

 

本文来源于:面向前端开发者的V8性能优化-变化吧门户
特别声明:以上文章内容仅代表作者本人观点,不代表变化吧门户观点或立场。如有关于作品内容、版权或其它问题请于作品发表后的30日内与变化吧联系。

在寻找靠谱的建站服务?

提供建站全方位托管服务,告诉我你的需求,剩下的交给我帮你处理,建站过程中碰到的任何小问题免费处理,网站所有数据由你自己掌握,还提供网站SEO指导,犹豫什么呢?联系我吧。

 

  • 赞助本站
  • 微信扫一扫
  • weinxin
  • 加入Q群
  • QQ扫一扫
  • weinxin
二叶草
nginx解析漏洞 优化

nginx解析漏洞

phpstudy(小皮模板存在nginx解析漏洞) 影响版本 phptsuy8.1.07的Nginx1.5.11版本影响版本 phptsuy8.1.07的Nginx1.5.11版本 phpstudy介...
网站SEO优化基础流程(新手必看) 优化

网站SEO优化基础流程(新手必看)

宝塔面板搭建一个获取网站的Favicon图标的APIgetFavicon是一个可以获取网站的Favicon图标并显示在你的网页上的项目。安装方法很简单,属于开箱即食。这篇文章还是基于宝塔面板来搭建。 ...

发表评论