11

小而精之QuickJS JavaScript引擎及周边研究(I)

 3 years ago
source link: https://blog.csdn.net/innost/article/details/98491709
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

缘起

研究ART JVM的时候,我一直有一个遗憾,总感觉对Java语言的理解不深刻,从而我个人觉得自己对JVM的理解还处于相对初级的阶段——只能顺着代码里的执行流程去看。这个水平的话,改点小bug或许没问题,但如果将来Java加了新的语言特性,能知道如何在JVM中实现吗?

一句话,我在研究ART时深刻感受到了更多自己不懂的东西。显然,一门语言除了语言自己外还包括其它一些至关重要的知识——在这里,我姑且统称它们为周边。比如,对Java语言而言,JVM就是Java的周边。没有JVM,光弄个Java语言出来毫无作用。这些东西共同构成了一个完整的,严密的体系。

绝大部分情况下,我们使用一门语言,附带用它对应的周边就能完成相应的工作。这就好像我们买了电视机后,只要会用它就行了,而无需关注这台电视机是怎么生产出来的,生产一台电视机有哪些套路之类的事情。

但是,我个人是非常好奇为什么谷歌这样的公司好像很轻易就能搞出ART JVM、搞出go、搞出dart,甚至日本人都能搞出Ruby来,它们背后有什么套路吗?结合上面所述我在ART研究中留下的诸多遗憾,我想要投入一点点时间来了解一下。或许最终得不到一个完美的答案,也没想过将来有一天能自己搞出一门语言及周边,我只是觉得应该有人要关注和了解这些我认为是核心技术基础的东西——这就是本篇及后续相关文章的缘起。

为什么没有选择Dart

我最开始想选择Dart作为一门语言及周边的研究对象。Dart语法简单,而且拥有当代语言的很多特点。不过当我下载完Dart全套周边后,发现它对像我这样的独立研究者来说太过于复杂了:

  • 首先也是最让我头疼的一点,Dart周边中的一个重要部分——编译工具看起来比较复杂。Dart本身的编译依赖谷歌内部使用的一个工具gn,再加上依旧是脱胎于谷歌Chrome的编译工具ninja——ninja这玩意早就在Android源码编译中用上了。这些东西都是谷歌系的,有些开源,有些没有。我直觉感觉它们在未来某个研究节点上会跳出来阻碍我的研究步伐。

  • 另外,Dart的源码太多,支持AOT和JIT,对我这次研究的目标来说过于复杂,耗时会很多。

怎么办?幸好看到了生猛!FFmpeg 开发者徒手撸了一个 JS 引擎文章。经过分析,我觉得QuickJS应该是一个不错的研究对象——虽然目标语言变成是JavaScript,但JS其实发展蛮快,也吸收了现代语言的新特点。而Dart其实也参考了JS及其衍生语言的很多东西。简单来说,QuickJS作为研究对象的优势在于:

  • QuickJS引擎为纯C写的,只有少数几个文件,整个工程下包括测试用的,一共包含十几个.C文件。相当精简。

  • 所有东西的编译用一个Makefile文件即可,相比复杂的Android.mk,简直是简单到极致了,自己要动手改点什么东西也是容易得很。

  • 相关标准文档丰富。

所以,看来我在ART中的遗憾将通过JavaScript和QuickJS来弥补了。我把整个系列称之为小而精QuickJS JavaScript引擎及周边研究

作为开篇,我先介绍下我从QuickJS工程里看到的一些东西。

QuickJS总体情况介绍

先说QuickJS的作者,貌似有两位,其中的Fabrice Bellard是大牛。Qemu、FFmpeg的作者。这一辈子有FFmpeg一个就足以青史留名了。我简单从百度百科上摘抄了Bellard的履历。感觉上面这些语言都太简陋了,完全无法体现Bellard的厉害。你们知道就好。

aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9LeUJpYTNZZ3N5aGM4ZTVuMkNZZW5lanFBbWdvamRDY3F0YmFaSEYxM2ZXbEY3TVYzTDhpY1VPWXI3c1I5bjFuQzJybUZXWGpTOE9sY1dTRFZ0WEU1RGFnLzY0MD93eF9mbXQ9anBlZyZ0cD13ZWJwJnd4ZnJvbT01Jnd4X2xhenk9MSZ3eF9jbz0x

接下来我们了解下QuickJS的情况。QuickJS放在https://bellard.org/quickjs/上,没有使用github,貌似也没有用git管理源码。

我用SourceCounter对QuickJS的C代码进行了统计,情况如下:

aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9LeUJpYTNZZ3N5aGM4ZTVuMkNZZW5lanFBbWdvamRDY3FxeE55RTNRcWtLcU4wcW1wSGlhc3N0SlFuTFZLU3BHQzdLcVE1ZjNoRmljWUkwUjdBbXBBWFFMQS82NDA_d3hfZm10PWpwZWcmdHA9d2VicCZ3eGZyb209NSZ3eF9sYXp5PTEmd3hfY289MQ

上面是QuickJS中.c/.h文件的情况,也就是QuickJS引擎的全部文件了。按源码文件函数进行了排序。quickjs.c是我们以后要重点分析的对象。加上注释才4.8万行。作一个牛头不对马嘴的对比,ActivityManagerService.java代码在2.7万行左右。

再看全部的统计情况:

aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9LeUJpYTNZZ3N5aGM4ZTVuMkNZZW5lanFBbWdvamRDY3FMaWEwSDBvYmJYdzRiRmZYejh6ajBYS0RlWUtOWExac2p5bmlhVWFiV095bVZJZFhmTDlNdndFQS82NDA_d3hfZm10PWpwZWcmdHA9d2VicCZ3eGZyb209NSZ3eF9sYXp5PTEmd3hfY289MQ

一共13个.c文件,12个.h文件。.c文件的有效代码总行数(即不包括注释,空行)为5.9万行。这个量其实不算少了。这回我们可以做一个正儿八经的对比,以Android 7.0中的ART虚拟机代码为例,ART虚拟机是C++11写的,.cc文件(C++源码)的有效总代码函数为236744行。差不多是quickJS的4倍左右。

QuickJS的编译结果

根据Makefile和文档说明,QuickJS的编译结果如下:

aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9LeUJpYTNZZ3N5aGM4ZTVuMkNZZW5lanFBbWdvamRDY3ExQmpZb3ZoNG5EUXM1N2RXT1FodWI4VkJCUzFQdkF4UFh5b0U4NkpTTDNwYW1XZVpLSGliUjd3LzY0MD93eF9mbXQ9anBlZyZ0cD13ZWJwJnd4ZnJvbT01Jnd4X2xhenk9MSZ3eF9jbz0x

Makefile里边的内容挺多,但逻辑非常简单,一看便知。当执行完makemake install后,会生成两个文件夹——binlib。注意,这两个文件夹的路径位置可以在Makefile里修改。

  • bin是quickjs的可执行文件,包括qjsqjscqjsbnqjsbnc四个可执行文件。

  • lib下是quickjs的静态库,这样我们可以在很方便的将quickjs引擎集成到其它模块里,真的是体贴周到!

根据文档说明(恩,以后会对qjs进行细致的剖析):

  • qjs是一个纯解释执行的JS引擎。

  • qjsbn中的bn是Big Num的意思。

  • qjscquickJS compiler的意思。这里要特别说明,qjsc的compiler并不是ART虚拟机中的将字节码编译成机器码那样的编译器。它只是把目标js文件和quickjs引擎打包为一个可执行文件而已。这个可执行文件的运行只不过是quickjs引擎去解释执行打包到这个可执行文件里的目标js文件罢了。

QuickJS到底好不好?

Bellard没有自吹自擂,自卖自夸。quickjs到底好不好?是他自己说了算吗?不是,quickJs老实实跑了benchmark。

这一点我必须要吐槽下,国内好多公司搞了“新技术”出来,就是非常非常笼统得说比某某好了多少。请问:

  • 这个好是以什么为标准的?标准是谁制订的?除了你自己测出来之外,其他人可以独立测试并得出类似的结果吗?

  • 什么东西都有好有坏,请问你的改进有什么副作用没有?

  • 今天的超越,会不会明天很快被竞争对手超越?比如,ios 13上APP性能不是又提升不少了吗?这一点其实还蛮重要。比如咱们费心费力投入大量资源搞出来的一个优化,如果竞争对手非常容易就超越,还有啥好说的?搞不好人家一个降维打击顺带就把咱们的优势给颠覆掉了...

我觉得这是一个严肃的问题。技术是很公正的东西,将来我们真搞出个好东西,要走出国门去说服鬼子,恐怕也要老老实实去测试,通过客观结果来评价的路。

言归正传,我们看quickjs的benchmark结果。这里包含的周边知识还很多。下面是quickJS给出的benchmark结果。恩,还有和竞品的数据对比。

aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9LeUJpYTNZZ3N5aGM4ZTVuMkNZZW5lanFBbWdvamRDY3FwdkdIWEVpYXprVXpLVFVCOGRCU0FmRmhLMm5BaWFGVlhyS21naWFXTEZZdGs3WHBpYTVyYVBhUFVBLzY0MD93eF9mbXQ9anBlZyZ0cD13ZWJwJnd4ZnJvbT01Jnd4X2xhenk9MSZ3eF9jbz0x

猴急的可以先看上面表格的最后一行,是各种JS引擎各个benchmark测试项的跑分结果汇总,分数越高越好。除了宇宙最强的V8外,quickJS居然离Facebook的Hermes没差几分钱的距离。

如果你是个细心人,你会发现这个表格背后的含义远超跑分数据:

  • 咋有这么多个JS Engine?它们都是些什么?

  • 第一列里出现的各种Benchmark是什么意思?

我们先介绍被Bellard列为竞品的JS引擎都有哪些

  • DukTape:https://duktape.org,小巧,内存占用小,可方便集成到其它模块中。

  • XS:https://www.moddable.com,用于IoT嵌入式低端设备的JS引擎。官网明确说了,用于微控制器(microcontroller)。

  • MuJS:https://mujs.com,和DukTape类似,轻量级的,可方便集成到其它模块中的JS解释器。

  • JerryScript:https://jerryscript.net,也是用于IoT设备的JS引擎

  • Hermes:https://hermesengine.dev, Facebook搞出来的,为了Android平台上的React Native而生

  • V8:https://v8.dev, 应该是目前地球上最高技术水准的JS引擎了。

V8我顺带说一下。目前在手机行业和编译优化方面,华为和VIVO各搞了些东西,华为叫方舟编译器,VIVO叫编译增强技术。但到目前为止都不知道它们具体是什么,也没有看到测试数据。

我看了下V8网页上关于编译器的说明, https://v8.dev/blog/ignition-interpreter。意思是将代码全部翻译成机器码会导致内存占用过大,所以V8在这方面做了大量的改进和优化,并且,还没有停止继续优化和探索的脚步。

V8在编译领域的积累直接被ART虚拟机继承了。ART虚拟机里的字节码到机器码的编译模块和V8非常类似,有些类名,数据结构都一样。我怀疑dart的AOT编译也是吸收和继承了V8。编译器是一门语言周边中的重要一环...。

让我感到吃惊的是,V8发展到这个地步了,居然还在精益求精,是因为领导下了什么KPI指标吗?我感觉不是,他们这些牛人可能就是有一种比较纯粹的、想做得更好的想法或者精神,以至于可以投入这么些人,这么专情的去开发。这种精神是可以叫工匠精神的,但工匠这个词在学而优则仕的中国文化里并不是很讨喜。所以,我们叫它大师精神好了。

接下来,我们了解下不同的Benchmark测试项了。这里,你能看到更多不一样的东西。表格中的Benchmark测试项源自v8的benchmark。它们是精心挑选出来的。

  • 最全的信息在https://developers.google.com/octane/benchmark。

  • 国内朋友可以看https://wiki.mozilla.org/V8bench_Info。

另外,v8 benchmark的测试内容还在持续增加以更多更全的测试项以模拟各种使用情况。

这里简单说明下图中列出的Benchmark测试项(上面列的两个关于V8 bench info的网页中有详细介绍,我这里只是搬运工):

  • Richards:人名,代表剑桥教授Martin Richards,这哥们在80年代就写了这个测试case。其模拟了一个多任务OS的内核以及任务调度的场景 。对JS来说,它考验的是方法调用,属性查找等的处理能力。

  • DeltaBlue:这个和数学有很强的关系,叫one-way constraint solver,什么单向约束解决器。也不知道为什么选这个东西,但我感觉肯定不是拍脑袋选的,这个测试项主要考察JS引擎对多态(polymorphism)的处理能力。

  • Crypto:加解密测试,来自Tom Wu,看起来像个华人。它主要测试JS引擎对比特位操作的能力。

  • RayTrace:一个光线追踪程序,重点测验JS引擎执行对象分配、初始化和GC的能力。

  • EarleyBoyer:也是一个什么constraint solver。它会创建很多小的对象,所以很考验JS引擎对象创建、gc的能力。

  • RegExp:考验JS引擎对正则表达式的处理能力。

  • Splay:叫什么扩展树,主要考验JS引擎GC的能力。

  • NavierStokes:纳维叶-斯托克斯方程,号称物理界最难的方程。我到目前为止这辈子和这个鬼东西有半毛钱关系的也就是考试刚及格的工程水文学中好像是碰到过它。它主要考验JS引擎的读写numeric数组,浮点计算的能力。

我上面解释得可能不全对,感兴趣的还是主要看上面给的谷歌链接。必须要说的是,这几个case应该不是随便选出来的,至少Bellard是认可这些case的。另外,V8官网也说了,还在针对每种case做进一步优化....

好了,上面是我从QuickJS Benchmark数据里发掘都的一些信息。这些东西对我的刺激比较大,我们这边还在想年龄过了35了,不应该在第一线干活了,也没资格谈工匠了....。可是好像人家在这么底层,好像也带来不了什么赚钱价值的事情上搞来搞去的。

昨天CSDN发了一篇给吴军博士一个采访,也谈到了类似的问题。他说暂时无解...。说实话,这个问题大家都知道,而且都知道好多年了。我本来还指望开复老师,吴博士能给个哪怕是最后走不通的方法让我们好歹去尝试解决啊....一上来就判死刑,这就是让我们《见识》吗?。

简单介绍ECMAScript

JS的标准是ECMAScript。ECMA是一个标准化组织,下面有好多标准。ECMAScript是形成JavaScript语言基础的脚本语言。ECMAScript是由Ecma国际标准组织以ECMA-262和ECMA-402规范的形式进行标准化的。

所以QuickJS在JS兼容性方面的能力就是通过运行ECMA的262相关测试代码来保证的。QuickJS中测试的代码中叫test262(测试代码要单独下,比较多。其中还有一个文件夹叫test262o,o是obsolete的意思,是针对ES5的测试)。关于QuickJS对262的兼容性测试我不说太多。我们说点别的。

  • 第一,Bellard之所以能写出一个JS引擎,必须对JS语言有比较深刻的认识。就好像一个连酱油、味精、盐都不知道的人不会成为一个好厨师一样。这其实一句正确的废话,但现实情况就是很多人并不会想到这个。比如,我知道现在有很多公司的朋友们被要求去优化dart,魔改flutter底层,搞了各种酷炫的方案和成果,但好像有些人连原来的东西都没看明白.....这或许是国产特色的“实用新型”吧....。
  • 第二,我很好奇ECMAScript是谁来维持和发展它的呢?来看下图。
aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9LeUJpYTNZZ3N5aGM4ZTVuMkNZZW5lanFBbWdvamRDY3FaeDlNd1dBd3BzTHhwMnBoWlUyeWNVS2liRjB1Umt2ZXRPdUpvMWxKU3AyZ1locEJ4T0N6RXpRLzY0MD93eF9mbXQ9cG5nJnRwPXdlYnAmd3hmcm9tPTUmd3hfbGF6eT0xJnd4X2NvPTE

ECMA分为多个Technical Committe(技术委员会),TC下有分几个Technical Group(技术组?不知道官方翻译是什么)。比如,ECMAScript由TC39来管,下面有两个TG。从图中还可以看到,Dart现在也交给ECMA了,委员会编号TC52。将来我们搞个语言,是不是也要考虑给ECMA?

那么,TC39里都有什么人?油管视频https://www.youtube.com/watch?v=Hj5q8uyqGYc揭秘了。我截了个图。

aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9LeUJpYTNZZ3N5aGM4ZTVuMkNZZW5lanFBbWdvamRDY3EzS3lENFdqUEtUVzk4d1Q0WkNBSlp0aWJDbHY3V1FEaWJPQUVGbTliZkExQWlhVDRpY2g5RFRKUmN3LzY0MD93eF9mbXQ9anBlZyZ0cD13ZWJwJnd4ZnJvbT01Jnd4X2xhenk9MSZ3eF9jbz0x

左边第一个妹子是JSConf的主持人。后面几个都是TC39的主要成员吧(应该不是长得好看的才能上台,鬼子好像不太喜欢搞"妙可"那一套把戏)。这些人都是来自不同公司,有谷歌、bloomberg、Mozillar。搞这个东西还是要投入很多时间和精力的,各位可以把这个视频看完。好在他们不用对哪个上峰负责,只要追求本心的,实事求是的把事情干好就完了。幸福的一群鬼子....。

后续的安排

这是一篇略带戏谑的文章,但我确实在ART研究中发现了极大不足。知道的越多,发现不知道的也越多。我们离世界顶尖距离差得还真的有点远。而且,你会发现他们这些东西并不是单独的,孤立的,而是一整套规范,一个完整严密的体系,而且还在不断发展,更快,更好,更强。这就让我感到比较可怕了。

我个人能力有限,但我也很不愿看到我们就这么被吴博士判了死刑。总该做点什么总是能做点什么吧??我先自己动手了。接下来我想比较详细的分析QuickJS引擎,我会尽量将它和JS语言的特点结合起来,看看大师是怎么考虑问题的。这或许对你老板、领导要求的“实用创新"没什么太大价值,但你知道还有人愿意去研究这些东西,以后有机会也可以一起来做。比如,有愿意搞清楚上面各个Bechmark测试项的吗?可以做的事情很多,一件一件做就好了。

最后的最后

  • 我期望的结果不是朋友们从我的书、文章、博客后学会了什么知识,干成了什么,而应该是说,神农,我可是踩在你的肩膀上得喔。

  • 关于学习方面的问题,我已经讨论完了。后面这个公众号将对一些基础的技术,新技术做一些学习和分享。也欢迎你的投稿。不过,正如我在公众号“联系方式”里说的那样——郑渊洁在童话大王《智齿》里有一句话令我印象深刻,大意是“我有权保持沉默,但你说的每一句话都可能成为我灵感的源泉”。所以,影响不是单向的,很可能我从你那学到的东西更多。

                                                         

aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9LeUJpYTNZZ3N5aGNaa0RIdWRNdGliQmtrWUg5NW8yQVlVd1BnY1lYMGd0SjNwS3pBakFrZmtqWlQ2QUFsaWFEWFpsZDVGVFZuMkZaMnpyYW1FaEFzUnFpY0EvNjQwP3d4X2ZtdD1qcGVnJnRwPXdlYnAmd3hmcm9tPTUmd3hfbGF6eT0xJnd4X2NvPTE

                                                                   神农和朋友们的杂文集

                                                                   长按识别二维码关注我们


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK