作为Web浏览器性能和代码重用的解决方案,Emscripten 是一个独特的 LLVM

  • 栏目:基础 时间:2020-04-17 04:43
<返回列表

Zig 0.5.0 发布了。

Emscripten 是一个独特的 LLVM 后端,它可以将 LLVM 字节码编译成 JavaScript(asm.js),往上追溯其实也就是通过 Clang 将 C 和 C++ 代码编译成 JavaScript(asm.js),可以大大简化现有代码在 Web 时代的重用。除了 asm.js,Emscripten 还支持 WebAssembly 这一更加先进的 Web 技术,通过与 asm.js 类似的机制,Emscripten 可以生成 WASM 二进制字节码。

图片 1

图片 2

图片 3

引用作者:张敏,Intel开源技术中心Web团队软件技术经理,原Opera Software软件经理,在浏览器及Web Runtime领域工作10年,专注于Web及开源技术。责编:陈秋歌本文为《程序员》原创文章,未经允许不得转载,更多精彩文章请订阅《程序员》在浏览器之争中,Chrome凭借JavaScript的卓越性能取得了市场主导地位,然而由于JavaScript的无类型特性,导致其运行时消耗大量的性能做为代价,这也是JavaScript的瓶颈之一。WebAssembly旨在解决这一问题。本文从WebAssembly的起源到开发实践对其做全面探究,帮助开发者对WebAssembly有全面的了解。缘起让我们从浏览器大战说起。微软凭借Windows系统捆绑Internet Explorer的先天优势击溃Netscape后,进入了长达数年的静默期。而Netscape则于1998年将Communicator开源,并由Mozilla基金会衍生出Firefox浏览器,在2004年发布了1.0版本。从此,第二次浏览器大战拉开帷幕。这场大战由Firefox浏览器领衔,Safari、Opera等浏览器也积极进取,Internet Explorer的主导地位首次受到挑战。2008年Google推出Chrome浏览器,不但逐步侵蚀Firefox的市场,更是压制了老迈的Internet Explorer。在此次大战之后的2012年,StatCounter的数据指出Chrome以微弱优势超越Internet Explorer成为世界上最流行的浏览器。分析Google Chrome浏览器战胜Internet Explorer的原因,除了对Web标准更友善的支持外,卓越的性能是其中相当重要的因素,而浏览器性能之争的本质则体现在JavaScript引擎。此前,JavaScript引擎的实现方式经历了遍历语法树到字节码解释器等较为原始的方式,将每条源代码翻译成相应的机器码并执行,并不保存翻译后的机器码,使得解释执行很慢。2008年9月,Google发布了V8 JavaScript引擎。V8被设计用于提高Web浏览器中JavaScript的执行性能,通过即时编译JIT技术,在执行时将JavaScript代码编译成更为高效的机器代码并保存,下次执行同一代码段时无需再编译,使得JavaScript获得了几十倍的性能提升。然而,JavaScript是个无类型的语言,这直接导致表达式c=a+b有多重含义:a、b均为数字,则算术运算符+表示值相加;a、b为字符串,则+运算符表示字符串连接;…表达式执行时,JIT编译器需要检查a和b的类型,确定操作行为。若a、b均为数字,JIT编译器则将a、b确认为整型,而一旦某一变量变成字符串,JIT编译器则不得不将之前编译的机器码推倒重来。由此可见,JavaScript的无类型特性建立在消耗大量性能代价的基础之上。即便JIT编译器在对变量类型发生变化时已进行相应优化,但仍然有很多情况JavaScript引擎未进行或无法优化,例如for-of、try-catch、try-finally、with语句以及复合let、const赋值的函数等。由此可见,JavaScript的无类型是JavaScript引擎的性能瓶颈之一,改进方案有两种:一是设计一门新的强类型语言并强制开发者进行类型指定;二是给现有的JavaScript加上变量类型。微软开发的TypeScript属于第一种改进方案。它是扩展了JavaScript特性的语言,包含了类型批注,编译时类型检查,类型推断和擦除等功能,TypeScript开发者在声明变量时指定类型,使得JavaScript引擎能够更快将这种强类型的语言编译成弱类型。看看第二种方案:代码1代码1表示带有两个参数的JavaScript函数,和通常JavaScript代码不同的地方在于a=a | 0及b=b | 0,以及返回值后面均利用标注进行了按位OR操作。这么做的优点是使JavaScript引擎强制转换变量的值为整型执行。通过标注加上变量类型,JavaScript引擎就能更快地编译。既然增加变量类型能够提升Web性能,有没有办法将静态类型代码例如C/C++等转换成JavaScript指令的子集呢?上面的这段代码恰恰是作为JavaScript子集的asm.js,由代码2的C语言编译而来:代码2事实上,早在1995年起就已经有Netscape Plugin API在内的可以使用浏览器运行C/C++程序的项目在开发。而2013年问世的asm.js是目前较为广泛的方案。asm.js是一种中间编程语言,允许用C/C++语言编写的计算机软件作为Web应用程序运行,并保持更好的性能,而Mozilla Firefox从版本22起成为第一个为asm.js特别优化的网页浏览器。Google也同样在为原生代码运行在Web端而努力。Google Native Client采用沙盒技术,让Intel x86、ARM或MIPS子集的机器码直接在沙盒上运行。它能够在无需安装插件的情况下从浏览器直接运行原生可执行代码,使Web应用程序可以用接近于机器码运作的速度来运行。而Google Portable Native Client则稍有变化,通过一些前端编译器将C/C++源代码编译成LLVM的中间字节码而不是x86或ARM代码,并且进行优化以及链接。有了类型支持,第二种方案性能提升潜力远远大于第一种。然而,无论是asm.js或现有PNaCl的解决方案,都面临着一些缺陷或其他浏览器不支持的窘境,而2016年10月对Chromium问题跟踪代码的评论更是表明,Google Native Client小组已被关闭。作为Web浏览器性能和代码重用的解决方案,asm.js及PNaCl都没能被普遍接受,那么有没有上述表格中的特性全部占优,且跨厂商的解决方案呢?WebAssembly旨在解决这个问题。新时代WebAssembly是一种新的适合于编译到Web的,可移植的,大小和加载时间高效的格式。这是一个新的与平台无关的二进制代码格式,目标是解决JavaScript性能问题。这个新的二进制格式远小于JavaScript,可由浏览器的JavaScript引擎直接加载和执行,这样可节省从JavaScript到字节码,从字节码到执行前的机器码所花费的即时编译JIT时间。 作为一种低级语言,它定义了一个抽象语法树,开发人员可以以文本格式进行调试。WebAssembly描述了一个内存安全的沙箱执行环境,可以在现有的JavaScript虚拟机中实现。 当嵌入到Web中时,WebAssembly将强制执行浏览器的同源和权限安全策略。因此,和经常出现安全漏洞的Flash插件相比,WebAssembly是一个更加安全的解决方案。WebAssembly可由C/C++等语言编译而来。此外,WebAssembly由Google、Mozilla、微软以及苹果公司牵头的W3C社区组共同努力,基本覆盖主流的浏览器厂商,因此其可移植性相较Silverlight等有极大提升,平台兼容问题将不复出现。在Web平台的很多项目中,对于原生新功能的支持需要Web浏览器或Runtime提供复杂的标准化的API来实现,但是JavaScript API往往较慢。使用WebAssembly,这些标准API可以更简单,并且操作在更低的水平。例如,对于一个面部识别的Web项目,对于访问数据流我们可以由简单的JavaScript API实现,而把面部识别原生SDK做的事情交由WebAssembly实现。需要了解的是,WebAssembly不是将C/C++等其他语言编译到JavaScript,更不是一种新的编程语言。探究asm.js上文的C语言求和代码经由编译器生成asm.js后如代码3所示。代码3上述代码转换为WebAssembly的文本格式稍显复杂,为了理解方便,我们从精简的asm.js开始。代码4wast文本文件将asm.js代码转换为WebAssembly的文本格式 add.wast。代码4WebAssembly中代码的可装载和可执行单元被称为一个模块。在运行时,一个模块可以被一组import值实例化,多个模块实例能够访问相同的共享状态。目前文本格式中的module主要用S表达式来表示。虽然S表达格式不是正式的文本格式,但它易于表示AST。WebAssembly也被设计为与ES6的modules集成。一个单一的逻辑函数定义包含两个部分:功能部分声明在模块中每个内部函数定义的签名,代码段部分包含由功能部分声明的每个函数的函数体。WebAssembly是带有返回值的静态类型,并且所有参数都含有类型。上面的add.wast可以解读为:声明了一个名为$add的函数;包含两个参数a和b,两者都是32位整型;结果是一个32位整型;函数体是一个32位的加法:上面是局部变量$a得到的值;下面是局部变量$b得到的值;由于没有明确的返回节点,因此return是该加法函数的最后加载指令。二进制Wasm文件如图1所示,由C语言求和代码经过编译生成二进制文件,通读文件可以找到相应的头部、类型、导入、函数以及代码段等。通过JavaScript API载入Wasm二进制文件后,最终转换到机器码执行。图1 经过编译的二进制文件工具链开发人员现在可以使用相应的工具链从C / C ++源文件编译WebAssembly模块。WebAssembly由许多工具支持,以帮助开发人员构建和处理源文件和生成的二进制内容。EmscriptenEmscripten是其中无法回避的工具之一,如图2所示。在图2中,Emscripten SDK管理器用于管理多个SDK和工具,并且指定当前正被使用到编译代码的特定SDK和工具集。图2 Emscripten工具链流程图及生成JavaScript流程Emscripten的主要工具是Emscripten编译器前端,它是例如GCC的标准编译器的简易替代实现。Emcc使用Clang将C/C++文件转换为LLVM字节码,使用Fastcomp把字节码编译成JavaScript。输出的JavaScript可以由Node.js执行,或者嵌入HTML在浏览器中运行。这带来的直接结果就是,C和C++程序经过编译后可在JavaScript上运行,无需任何插件。WABT和Binaryen除此之外,对于想要使用由其他工具生成的WebAssembly二进制文件感兴趣的开发者,目前官方额外提供了另外两组不同的工具:WABT ——WebAssembly二进制工具包;Binaryen——编译器和工具链。WABT工具包支持将二进制WebAssembly格式转换为可读的文本格式。其中wasm2wast命令行工具可以将WebAssembly二进制文件转换为可读的S表达式文本文件。而wast2wasm命令行工具则执行完全相反的过程。Binaryen则是一套更为全面的工具链,是用C++编写成用于WebAssembly的编译器和工具链基础结构库。WebAssembly是二进制格式并且和Emscripten集成,因此该工具以Binary和Emscript-en的末尾合并命名为Binaryen。它旨在使编译WebAssembly容易、快速、有效。它包含且不仅仅包含下面的几个工具。图3 Binaryen生成WebAssembly流程wasm-as:将WebAssembly由文本格式编译成二进制格式;wasm-dis:将二进制格式的WebAssembly反编译成文本格式;asm2wasm:将asm.js编译到WebAssembly文本格式,使用Emscripten的asm优化器;s2wasm:在LLVM中开发,由新WebAssembly后端产生的.s格式的编译器;wasm.js:包含编译为JavaScript的Binaryen组件,包括解释器、asm2wasm、S表达式解析器等。Binaryen目前提供了两个生成WebAssembly的流程,由于emscripten的asm.js生成已经非常稳定,并且asm2wasm是一个相当简单的过程,所以这种将C/C ++编译为WebAssembly的方法已经可用。图4 Emscripten+Binaryen生成WebAssembly的完整流程由此可见,Emscripten以及Binaryen提供了完整的C/C++到WebAssembly的解决方案。而Binaryen则帮助提升了WebAssembly的工具链生态。提示由于WebAssembly正处于活跃开发阶段,各项编译步骤和编译工具会有大幅变更和改进,相信最终的编译工具和步骤会趋于便捷,开发者需要留意官方网站的最新动态。实战Linux和mac OS平台编译原生代码到WebAssembly可由如下步骤实现。编译环境准备操作系统必须有可以工作的编译器工具链,因此需要安装GCC、cmake环境,此外Python、Node.js及Java环境也是需要的。图5 编译环境安装如果是以其他方式安装了Node.js,可能需要更新~/.emscripten文件的NODE_JS属性。安装正确的emscripten分支要编译原生代码到WebAssembly,我们需要emscripten的incoming分支。由于emscripten不仅仅是用于WebAssembly的编译工具链,选择正确的分支尤为重要。图6 安装emscripten的incoming分支其中URLTO具体的URL是。处理安装异常可运行emcc -v命令进行验证安装。如果遇到如图7所示的错误,表明带有JavaScript后端的LLVM编译器并未被生成。图7 emcc -v命令报错图8 emcc -v命令报错解决方案通过图8步骤,可以解决该问题,并且在~/.emscripten 文件中修改如下配置:开始编译程序现在一个完整的工具链已经具备,我们可以使用它来编译简单的程序到WebAssembly。但是,还有一些其他注意事项:必须通过参数-s Wasm=1到emcc;除了Wasm二进制文件和JavaScript wrapper外,如果还希望emscripten生成一个可直接运行的程序的HTML页面,则必须指定一个扩展名为.html的输出文件。在编译之前,首先准备一个最基本的add.c程序,见代码6。代码6按代码7所示的命令编辑好add.c程序并编译:运行WebAssembly应用以Chrome浏览器为例,如果直接在浏览器内本地打开HTML文件,会有图9所示的错误:图9 XMLHttpRequest本地访问的跨域请求错误由于XMLHttpRequest跨域请求不支持file://协议,必须经由HTTP实际输出,可以由Python的SimplHTTPServer改进,见代码8:代码8在浏览器中输入并打开add.html,就能直接看到转换成WebAssembly的应用程序输出结果。创建独立WebAssembly默认情况下,emcc会创建JavaScript文件和WebAssembly的组合,其中JS加载包含编译代码的WebAssembly。对于C/C++开发人员,他们可能更倾向于创建独立的WebAssembly,用于JavaScript开发人员调用,见代码9。代码9上述命令运行后,我们可以得到独立的Wasm文件。需要说明的是,该参数仍然在开发中,可能随时发生规范和实现变更。JavaScript API调用从C/C++程序编译获得一个.wasm模块之后,JavaScript开发人员可以通过如下方式进行载入.wasm文件并执行。WebAssembly社区组也有计划通过Streams使用streaming以及异步编译,见代码10。代码10最后一行调用导出的WebAssembly函数,它反过来调用我们导入的JS函数,最终执行add(201700, 2),并且在控制台获得期望的结果输出。图10 WebAssembly求和函数在控制台的输出性能那么,WebAssembly的真实性能如何呢?首先我们用一直被用来作为CPU基准测试的斐波那契 数列来进行对比,这里使用的是性能较差的递归算法,在Node.js v7.2.1环境下,能够看到WebAssembly性能优势越发明显。图11 CPU基准测试反应WebAssembly的真实性能再看看最基本的1000毫秒时间内,求和计算的运算量统计,在同一台计算机的Firefox 50.1.0版本的运算结果如图12所示。图12 1000毫秒内求和计算的运算量统计尽管重复测试时结果不尽相同,重启浏览器并多次测试取平均值后依然可以看到WebAssembly的运算量比JavaScript快了近一个量级。Demo图13展示了Angry Bots Demo,它是由WebAssembly项目发布的一个Demo,由Unity游戏移植而来。图13 Angry Bots Demo / Google Chrome 55.0.2883.87通过如下方式可以体验WebAssembly在浏览器中的强大性能。即便Google Chrome较新的稳定版也已支持WebAssembly,还是推荐使用canary版及Firefox的nightly版进行测试。1、下载浏览器:1-1. Google Chrome;1-2. Mozilla Firefox;1-3. Opera;1-4. Vivaldi。2、打开 WebAssembly支持 :2-1. Google Chrome:chrome://flags/#enable-webassembly;2-2. Mozilla Firefox:about:config→接受→搜索javascript.options.wasm→设置为true;2-3. Opera:opera://flags/#enable-webassembly;2-4. Vivaldi:vivaldi://flags#enable-webassembly。访问:。使用W、A、S、D等键实现移动操作,点击鼠标进行射击。该WebAssembly游戏在浏览器中运行相当流畅,媲美原生性能。除了最新的浏览器开始对WebAssembly逐步支持外,Intel开源技术中心开发的Crosswalk项目早在2016年11月初的Crosswalk 22稳定版即已加入对WebAssembly实验性的支持,开发者可以使用该版本体验Angry Bots Demo。开发者WebAssembly对于Web有显著的性能提升,对于开发者尤其是前端或者JavaScript开发人员而言,并不意味着WebAssembly将会取代JavaScript。图14 WebAssembly与JavaScript引擎的关系WebAssembly被设计为对JavaScript的补充,而不是替代,是为了提供一种方法来获得应用程序的关键部分接近原生性能。随着时间的推移,虽然WebAssembly将允许多种语言被编译到Web,但是JavaScript的发展势头不会因此被削弱,并且仍然将保持Web的单一动态语言。此外,由于WebAssembly构建在JavaScript引擎的基础架构上,JavaScript和WebAssembly将在许多场景中配合使用。那么WebAssembly是不是仅仅面向C/C++开发者呢?答案依旧是否定的。WebAssembly最初实现的重点是C/C++,由Mozilla主导开发的注重高效、安全和并行的Rust也能在2016年末被成功编译到WebAssembly了,未来还会继续增加其他语言的支持,见代码11。代码11在未来,通过ES6模块接口与JavaScript集成,Web开发人员并不需要编写C++,而是可以直接利用其他人编写的库,重用模块化C++库可以像使用JavaScript中的modules一样简单。进展依据开发路线图,2016年10月31日,WebAssembly到达浏览器预览的里程碑。Google Chrome V8引擎及Mozilla Firefox SpiderMonkey引擎都已经在trunk上支持WebAssembly浏览器预览。2016年12月下旬,Microsoft Edge浏览器使用的JavaScript引擎ChakraCore v1.4.0启用了WebAssembly浏览器预览支持。而Webkit JavaScriptCore引擎对于该支持也在积极进行中。目前,WebAssembly社区组已经有初始二进制格式发布候选和JavaScript API在多个浏览器中实现。作为浏览器预览期间的一部分,WebAssembly社区组现在正在征求更广泛的社区反馈。社区组的初步目标是浏览器预览在2017年第一季度结束,但在浏览器预览期间的重大发现可能会延长该周期。当浏览器预览结束时,社区组将产生WebAssembly的草案规范,并且浏览器厂商可以开始默认提供符合规范的实现。预计在2017年上半年,四大主流浏览器对原生的WebAssembly支持将到达稳定版。具体到Google V8引擎的最新进展,asm.js代码将不再通过Turbofan JavaScript编译器而是编译到WebAssembly后,在WebAssembly的原生执行环境中执行最终的机器码。这种改变带来的好处有,为asm.js将预先编译带到了Chrome,且完全向后兼容。新的WebAssembly编译渠道重用了一些Turbofan JavaScript编译器后端部分,因此能够在少了很多编译和优化消耗的前提下,产生类似的代码。在Google Chrome中,WebAssembly将很快在Canary版中默认启用,开发团队也期望能够发布到2017年第一季度末的稳定版中。社区包含所有主要浏览器厂商代表的W3C Web——Assembly社区组于2015年4月底成立。该小组的任务是,在编译到适用于Web的新的、便携的、大小和加载时间高效的格式上,促进早期的跨浏览器协作。该社区组也正在将WebAssembly设计为W3C开放标准。目前,除了文中所述主流浏览器厂商Mozilla、Google、微软、及苹果公司之外,Opera CTO及Intel的8位该领域专家均参与了该社区组。当然,并不是只有社区组成员才能参与标准的制定,任何人都可以在做出贡献。展望由于主要的浏览器厂商对WebAssembly支持表现积极,并且都在实现WebAssembly的各项功能,因此在Web中高性能需求的应用例如在线游戏、音乐、视频流、AR/VR、平台模拟、虚拟机、远程桌面、压缩及加密等都能够获得接近于原生的性能。相信WebAssembly将会开创Web的新时代。

Zig 是一门通用编程语言,专为稳定性、可维护性和性能而设计,追求替代 C 语言在系统编程上的最佳地位。Zig 具有以下值得关注的特性:

原本 Emscripten 的 WebAssembly 后端用的是 fastcomp,但之前已经增加了 LLVM WebAssembly 后端选项,并且二者保持并行开发。近期 JS 引擎 V8 官方发表博客表示很快将默认使用 LLVM WebAssembly 作为 Emscripten 的 WebAssembly 后端,因为 LLVM WebAssembly 后端现在在大多数指标上都已经超越了 fastcomp 后端。V8 团队介绍了具体原因,并给出了基准测试结果:

链接更快

0.5.0 版本经过 6 个月打磨,包括了 1541 次 commit,带来了许多新的内容。

LLVM WebAssembly 后端加上wasm-ld可以完全支持 WebAssembly 目标文件增量编译。fastcomp 在字节码文件中使用 LLVM IR,这意味着在链接时,所有 IR 都将由 LLVM 编译,所以 fastcomp 链接慢。另一方面,WebAssembly 对象文件 .o包含已编译的 WebAssembly,它们可以像原生链接一样链接,因此,链接步骤可以比使用 fastcomp 快得多。

Zig 跟随 LLVM 的步伐,前几天 LLVM 9 发布了,所以 Zig 目前升级到了 LLVM 9,不再兼容 LLVM 8。值得注意的是,这意味着 Zig 现在支持 RISC-V。同时 Zig 还可以以 Emscripten 为编译目标操作系统。Emscripten 是一个独特的 LLVM 后端,它可以将 LLVM 字节码编译成 JavaScript(asm.js),往上追溯其实也就是通过 Clang 将 C 和 C++ 代码编译成 JavaScript(asm.js),可以大大简化现有代码在 Web 时代的重用。除了 asm.js,Emscripten 还支持 WebAssembly 这一更加先进的 Web 技术,通过与 asm.js 类似的机制,Emscripten 可以生成 WASM 二进制字节码。

基准测试结果是 LLVM WebAssembly 链接速度是 fastcomp 的 7 倍:

目前 Emscripten 还不能自托管,之后可行的情况下,可以使用 WebAssembly 将其作为浏览器中的 Zig 沙箱选项。

图片 4

语言特性层面的主要更新包括:

速度更快,代码更小

LLVM WebAssembly 后端技术栈在基准测试中速度和代码大小都击败了 fastcomp。

完整更新内容查看发布公告:

图片 5

宏观基准测试结果是速度平均提升了 3.2%。

图片 6

总体而言,宏观基准代码大小平均减小了 3.7%,而在实际项目中,Cube 2 游戏引擎的 Web 移植版本 BananaBread,减小了 6% 以上,Doom 3 则减小了 15%。

这样的改进得益于:

支持所有 LLVM IR

fastcomp 可以处理由 clang 产出的 LLVM IR,但因为架构原因经常会在其它源上失败,特别是在将 IR 合法化为 fastcomp 可以处理的类型时。另一方面,LLVM WebAssembly 后端使用通用的 LLVM 后端基础结构,因此可以处理所有内容。

新的 WebAssembly 功能

fastcomp 在运行 asm2wasm之前编译出 asm.js,这意味着很难处理新的 WebAssembly 功能,如尾调用、异常与 SIMD 等。

来自上游更快的常规更新

使用上游 WebAssembly 后端意味着可以始终使用最新的 LLVM 上游,这意味着可以尽快获得 clang 中新的 C++ 语言功能、新的 LLVM IR 优化等。

切换到默认 LLVM WebAssembly 后,fastcomp 仍然是一个可选项,V8 团队表示最终会完全删除 fastcomp,这样做可以消除重大的维护负担,能够更多地关注于 WebAssembly 后端的新功能,并且加速 Emscripten 的一般改进。

详情查看原文:

(文/开源中国)    

上一篇:Chrome v79 以上版本中才能实现,微软正在开发两个新的滚动功能 下一篇:没有了

更多阅读

作为Web浏览器性能和代码重用的解决方案

基础 2020-04-17
Zig 0.5.0 发布了。 Emscripten 是一个独特的 LLVM 后端,它可以将 LLVM 字节码编译成JavaScript(asm.j...
查看全文

Chrome v79 以上版本中才能实现,微软正在

基础 2020-04-17
在开源平台和社区 Chromium上,微软已经成为仅次于谷歌的最大贡献者。近期针对 Chromium的改进...
查看全文

Python Web 框架大乱斗,Python 2 的最后一个

基础 2020-04-17
web.py 0.40 发布了。web.py 是一个小巧灵活的 Python框架,设计理念力求精简(Keep it simple andpower...
查看全文

友情链接: 网站地图

Copyright © 2015-2019 http://www.koi-bumi.com. 韦德体育有限公司 版权所有