前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >V8带来的JS性能优化

V8带来的JS性能优化

作者头像
Cloud-Cloudys
发布2020-07-07 15:16:23
1.8K0
发布2020-07-07 15:16:23
举报

两类型语言

一、编译型语言:在程序执行之前必须进行专门的编译过程,如C、C++、Java等。

编译型语言有以下特点:

  • 只需编译一次**就可以把源代码编译成机器语言,后边的执行无需重新编译,直接使用之前的编译结果就可以,因此执行效率比较高
  • 程序执行效率比较高,但比较依赖编译器,因此跨平台性差一些
  • 不同平台对编译器影响很大。
    • 16位系统下int是2个字节(16位),而32位系统下int占4个字节。
    • 32位系统下long类型占4字节,而64位下long类型占8个字节。

二、解释型语言:支持动态类型,弱类型,在程序运行的时候才进行编译,而编译前需要确定变量的类型,效率比较低,对不同系统平台有较大的兼容性。

解释型语言有以下特点:

  • 源代码不能直接编译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行
  • 程序不需要编译,程序在运行的时候才需要编译成机器语言,每执行一次都要编译一次
  • 运行效率一般相对比较低,依赖解释器,跨平台性好。

三、比较:

  • 一般,编译型语言的运行效率比解释型语言更高,但不能一概而论。
  • 编译型语言的跨平台特性比解释型语言差一些。

随着web相关技术的发展,JavaScript所要承担的工作也越来越多了,早就超越了“表单验证”的范畴,这就

更需要快速的解析和执行JavaScript脚本。V8引擎就是为解决这一引擎而生,在node中也是采用该引擎来解析JavaScript。

V8引擎

V8引擎使用C++开发,在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释行,V8将其编译成原生机器码,并且使用了如内联缓存等方法来提高性能。有了这些功能以后,JavaScript程序在V8引擎下的运行速度媲美二进制程序。V8支持众多操作系统,如windows、Linux、android等,也支持其他硬件架构,如ARM,X64等。具有很好的可移植性和跨平台特性。

数据表示

JavaScript是一种动态类型语言,在编译时并不能准确知道变量的类型,只可以在运行时确定,这就不像C++或者Java等静态类型语言,在编译时就可以确切的知道变量的类型。在运行时计算和决定变量的类型,会严重影响语言性能,这也就是JavaScript运行效率比C++或是Java低很多的原因之一。

在C++中,源代码需要经过编译才能执行,在生成本地代码的过程中,变量的地址和类型就已经确定,运行本地代码时利用数组和位移就可以存取变量和方法的地址,不需要再进行额外的查找,几个机器指令即可完成,节省了确定类型和地址的时间。JS是无类型语言,无法在执行时就知道变量的类型和地址,所以需要确定。

JS和C++的几个区别:

  • 编译确定位置。C++编译阶段确定位置偏移信息,在执行时直接存取;JS在执行阶段确定,而且执行期间可以修改对象属性。
  • 偏移信息共享。C++有类型定义,执行时不能动态改变,可共享偏移信息;JS每个对象都是自描述,属性和位置偏移信息都包含在自身结构中
  • 偏移信息查找。C++查找偏移地址很简单,在编译代码阶段,对使用的某类型成员变量直接设置编译位置;JS中使用一个对象,需要通过属性名匹配才能找到相应的值,需要更多的操作。

在代码执行过程中,变量的存取是非常普遍和频繁的,通过偏移量来存取,使用少数汇编指令就能完成,如果通过属性名匹配则需要更多的汇编指令,也需要更多的内存空间。

在JS中,除了boolean,number,string,null,undefined五种基本类型,其他的数据都是对象,V8使用一种特殊的方式来表示他们,进而优化JS的内部表达问题。

在V8中,数据的内部表示由数据的实际内容和数据的句柄构成。数据的实际内容是变长的,类型也是不同的;句柄大小固定,包含指向数据的指针。这种设计可以方便V8进行垃圾回收和移动数据内容,相比于直接使用指针,使用者使用句柄,只需要修改句柄中的指针,而指针的修改对使用者是透明的。

除少数数据(如整型数据)由句柄本身存储外,其他内容限于句柄大小和变长等原因,都存储在堆中。整数直接从value中取值,然后使用一个指针指向它,可以减少内存的占用并提高访问速度。

JavaScript对象在V8中的实现包含三部分:隐藏类指针,V8为JS对象创建的隐藏类;属性值指针,指向该对象的属性值;元素值指针,指向该对象的属性。

工作过程

在V8引擎中,JavaScript相关代码并非是一下完成编译的,而是在某些代码需要执行时才会进行编译,这就提高了响应时间,减少了时间开销。源代码先被解析成抽象语法树(AST),然后使用解释器或者编译器转换为Bytecode或者Machine code这种本地可执行代码。

编译阶段

JavaScript代码的编译过程:

1、Script类调用Compiler类的Compile函数为其生成本地代码;

2、Compile函数先试用Parser类生成AST,在使用FullCodeGenerator类生成本地代码;

3、本地代码与具体的硬件平台密切相关,FullCodeGenerator使用多个后端来生成与平台相匹配的本地汇编语言。

4、由于FullCodeGenerator通过遍历AST来为每个节点生成相应的汇编代码,缺失了全局视图,节点之间 优化也就无从谈起。

在执行编译之前,V8会构建众多的全局对象并加载一些内置的库来构建一个运行环境。而且在JavaScript源代码中,并非所有的函数都被编译成本地代码,而是延迟编译,在调用时才会编译。

运行阶段

为了性能提升,V8在生成本地代码后,使用数据分析器(profiler)采集一些信息,然后根据这些数据将本地代码进行优化,生成更高效的本地代码,这是一个逐步改进的过程。当发现优化后的代码还不如未优化的代码,V8会退回到原来的代码,也就是优化回滚。

运行阶段过程描述:

1、先根据需要编译和生成这些本地代码;

2、在V8中,函数是一个基本单位,当某个JS函数被调用时,V8会查找该函数是否已生成本地代码,如果已经生成,则直接调用该函数。否则,就生成该函数的本地代码。

3、执行编译后的代码为JavaScript构建JS对象,这需要Runtime类来辅助创建对象,并需要从Heap类分配内存。

4、借助Runtime类中的辅助函数来完成一些功能,如属性访问等。最后,将不用的空间进行标记清除和垃圾回收。

优化回滚

V8中有一个Ignition字节码编辑器,TurBoFan和Ignition结合起来共同完成JavaScript的编译,消除了CranShaft这个旧的编辑器,并让新的Ignition直接从字节码来优化代码,并当需要反优化的时候就直接反优化到字节码,而不需要考虑到JS源码。

隐藏类

V8借用了类和偏移位置的思想,将本来通过属性名匹配来访问属性值的方法进行了改进,使用类似C++编译器的偏移位置机制来实现,这就是隐藏类。

隐藏类将对象划分成不同的组,对于组内对象拥有相同的属性名和属性值的情况,将这些组的属性名和对应的偏移位置保存在一个隐藏类中,组内所有对象共享该信息,同时也可以识别属性不同的对象。

内嵌缓存

正常访问对象属性的过程:首先获取隐藏类的地址,然后根据属性名查找偏移值,然后计算该属性的地址。如果将之前查询的结果缓存起来,可以供再次访问,这就是内嵌缓存。

内嵌内存的思路是,将初次查找的隐藏类和偏移值保存起来,当下次查找的时候,先比较当前对象是否为之前的隐藏类,如果是直接使用之前的缓存结果,减少再次查表的时间。但是如果一个对象有多个属性,缓存失误的概率就会提高,因为属性的类型变化后,对象的隐藏类也会变化,与之前的缓存不一致,需要重新使用之前的方法查找哈希表。

快照

V8引入了快照机制,将内置的对象和函数加载之后的内存保存并序列化。序列化以后的结果很容易反序列化,经过快照机制的启动时间可以缩减几毫秒。快照机制也可以将一些开发者认为需要的JS文件序列化来减少处理事件。

总结

随着V8引擎的发展,我们可以在编程中注意一些问题来做到性能优化:

  • 类型。一个函数应该使用比较少的数据类型;对于数组,应尽量存放相同类型的数据,这样就可以通过偏移位置来访问。
  • 数据表示。简单类型的数据直接保存在句柄中,可以减少寻址时间和内存占用,如果可以使用整数表示的,尽量不要使用浮点数。
  • 内存。对不再使用的对象设置为null或使用delete方法来删除。设置为null可以做到手动垃圾回收,使用delete会引发额外操作。
  • 优化回滚。在执行多次后,不要出现修改对象类型的语句,尽量不要触发优化回滚,否则会大幅度降低代码的性能。
  • 新机制。使用JS引擎或者渲染引擎提供的新机制和新接口提高性能。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020.04.04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 两类型语言
  • V8引擎
  • 数据表示
  • 工作过程
    • 编译阶段
      • 运行阶段
      • 优化回滚
      • 隐藏类
      • 内嵌缓存
      • 快照
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档