前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从Rust到远方:ASM.js星系

从Rust到远方:ASM.js星系

作者头像
MikeLoveRust
发布2019-07-30 17:20:20
1.5K0
发布2019-07-30 17:20:20
举报

来源: https://mnt.io/2018/08/28/from-rust-to-beyond-the-asm-js-galaxy/

这篇博客文章是这一系列解释如何将Rust发射到地球以外的许多星系的文章的一部分:

  • 前奏,
  • WebAssembly 星系
  • ASM.js星系(当前这一集)
  • C 星系
  • PHP星系,以及
  • NodeJS 星系

Rust解析器将要探索的第二个星系是ASM.js。这篇文章会解释什么是ASM.js,怎样编译博客解析器到ASM.js以及如何在浏览器中和Javascript一起使用ASM.js. 使用ASM.js的目标是当作WebAssembly不可用的备用方案。我强烈建议你读读前一篇关于WebAssembly的文章,因为他们有很多共同的地方

#什么是ASM.js,为什么需要ASM.js

Web应用的主要语言是Javascript,任何想要运行在Web上的应用都必须编译成Javascript,比如游戏。但是这里有个问题:编译输出文件非常重(即使是WebAssembly),这样Javascript虚拟机将很难对这样的代码做优化,这就导致运行缓慢或者执行低效(可以考虑用游戏的规模来做例子)。而且在Javascript为编译目标的情况下,一些语言基础设施变得毫无用处,比如eval

如果有一种新的语言可以作为编译目标,而且也能被Javascript虚拟机运行会怎么样?这就是WebAssembly,但是在2013年的时候解决方案就是ASM.js:

asm.js, 是一个严格的Javascript子集,它能够被用作编译器输出的底层的,高效的目标语言。这个子语言高效的描述>了一个沙盒虚拟机,可以适用于内存不安全的语言,像C或者C++。静态和动态的组合校验让Javascript可以对有效 的asm.js代码使用一种叫做ahead-of-time(AOT)的编译时优化策略。

因此ASM.js程序其实只是一个普通的Javascript程序。它不是一个新的语言而是Javascript的一个子集。它可以被任何Javascript虚拟机执行。但是,有个特殊的魔法声明use asm;,会指示虚拟机用ASM.js引擎来优化这个程序。

ASM.js通过算术运算引入了类型作为标记系统。比如,x|0表示x是一个整数,+x表示x是一个双精度小数,fround(x)表示x是一个浮点数。下面的例子声明了一个函数fn increment(x: u32) -> u32:

代码语言:javascript
复制
function increment(x) {
    x = x | 0;
    return (x + 1) | 0;
}

另一个重要的区别是ASM.js以模块的方式来运行,以和Javascript隔离。这个模块是一个需要3个参数的函数:

  • stdlib,一个带有引用到标准库API的对象
  • foreign,一个带有用户定义功能的对象(比如通过WebSocket发送一些东西)
  • heap,一个表示内存的数组(因为内存是手动管理的)

但是它仍然是Javascript。因此一个好消息是如果你的虚拟机没有对ASM.js做优化,那么它运行起来就是普通的Javascript程序,如果有优化,那么你就可以得到不错的性能提升。

这个图片展示了3个基准测试,分别对于不同的Javascript引擎:Firefox, Firefox+asm.js, Google, 和Native。

记住ASM.js是被设计为一种编译目标。因此一般你不需要特别关心,因为那是编译器的活。对把C或者C++编译到Web的一个典型的编译和执行流程如下

Emscripten,如上图所示,是一个在这个Web平台演进过程中非常重要的一个项目。

它是一个用来编译输出asm.js和WebAssembly的工具链,基于LLVM之上,能够让C和C++程序以接近原生应用的速 度运行在Web上,而且不需要任何插件。

如果你和ASM.js或者WebAssembly打交道,迟早有一天你会看到这个名字。I will not explain deeply what ASM.js is with a lot of examples. I recommend instead to read Asm.js: The Javascript Compile Target by John Resig, or Big Web app? Compile it! by Alon Zakai. 我不会用大量的例子来深入的解释ASM.js.我推荐两本书:Asm.js: The Javascript Compile Target by John Resig, 或者 Big Web app? Compile it! by Alon Zakai.

我们的过程有点不一样。我们不会直接编译Rust代码到ASM.js,而是先编译为WebAssembly,然后再编译为ASM.js。

#Rust ? ASM.js

这个篇章会非常的短,应该说是最简单的一篇。要编译Rust到ASM.js你需要先编译到WebAssembly(参考前一篇文章)然后再编译WebAssembly二进制到ASM.js。

事实上真正需要ASM.js的地方是那些不支持WebAssembly的浏览器,比如IE。它只是我们在Web运行我们程序的一个后备方案。

下面看看这个流程:

  • 编译你的Rust项目到WebAssembly
  • 编译你的WebAssembly二进制为ASM.js模块
  • 优化和精简ASM.js模块

wasm2js会是你最好的朋友,它用来编译你的WebAssembly二进制为ASM.js模块,它属于Binaryen项目。下面假设我们已经有了程序的WebAssembly二进制,只需要运行下面的命令:

代码语言:javascript
复制
$ wasm2js --pedantic --output gutenberg_post_parser.asm.js gutenberg_post_parser.wasm

在这一步,gutenberg_post_parser.asm.js 有212kb。这个文件包含ECMAScript 6代码。注意这里因为考虑了老浏览器如IE,所以代码需要一点小小的转换来优化和精简ASM.js模块,我们用uglify-es工具,如下:

代码语言:javascript
复制
$ #  转换代码, 嵌入入到一个函数.
$ sed -i '' '1s/^/function GUTENBERG_POST_PARSER_ASM_MODULE() {/; s/export //' gutenberg_post_parser.asm.js
$ echo 'return { root, alloc, dealloc, memory }; }' >> gutenberg_post_parser.asm.js

$ # 精简代码.
$ uglifyjs --compress --mangle --output .temp.asm.js gutenberg_post_parser.asm.js
$ mv .temp.asm.js gutenberg_post_parser.asm.js

就像我们优化WebAssembly二进制一样,我们也可以gzip和brotli压缩输出文件:

代码语言:javascript
复制
$ # Compress.
$ gzip --best --stdout gutenberg_post_parser.asm.js > gutenberg_post_parser.asm.js.gz
$ brotli --best --stdout --lgwin=24 gutenberg_post_parser.asm.js > gutenberg_post_parser.asm.js.br

最终我们得到了下面的文件尺寸:

  • .asm.js: 54kb,
  • .asm.js.gz: 13kb,
  • .asm.js.br: 11kb.

都是非常小的!

思考一下,这里面涉及到了很多的转换:从Rust到WebAssembly到Javascript/ASM.js。。。工具的数量相对于工作量是非常少的。这展现了一个良好设计的流水线和不同工作组的人的良好合作。

题外话:如果你在读这篇博客,我假设你是一个开发者。既然如此,我很确定你可以花几个小时阅读源代码就像欣赏大师的画作。但是你有没有想过把Rust编译出来的Javascript输出是什么样的?

我可以说非常的喜欢它!

#ASM.js ? Javascript

输出的gutenberg_post_parser.asm.js文件包含一个唯一的函数叫做:GUTENBERG_POST_PARSER_ASM_MODULE,它返回一个对象包含了4个私有函数。

  1. root,语法根
  2. alloc,来分配内存
  3. dealloc,释放内存
  4. memory,内存缓冲区

如果你看过上一篇WebAssembly的文章那么你会对这些概念感到熟悉。不要指望root会返回一个完整的AST,它只会返回一个内存指针,数据需要进一步编解码,也需要用同样的方式对内存进行读写。是的,相同的方式。因此边界层代码完全是一样的。你是否还记得在WebAssembly中作为Javascript边界的Module对象?那和GUTENBERG_POST_PARSER_ASM_MODULE函数返回的完全是一样的。你甚至可以用这个对象替换。

所有的代码都在这里。它完全重用WebAssembly的Javascript边界层代码,只是Module有一些不一样,也没有加载WebAssembly二进制。结果是ASM.js边界代码只用了34行就写出来了,压缩后仅有218个字节。

#结论

我们已经看到ASM.js可以在只支持Javascript的环境中(像IE)作为WebAssembly的备用方案,并可适配环境打开或者关闭ASM.js优化。

输出的ASM.js文件以及其边界层代码都非常的小。设计上,ASM.js边界层代码重用WebAssembly的Javascript边界层代码,因此同样只有一小部分外部接口代码需要审查和维护,这非常的有用。

我们已经在前一篇文章中看到Rust很快的速度。我们也已经在比较WebAssembly版本和纯Javascript版本解析器中看到同样速度结论。然而这个结论是否也适用于ASM.js模块呢?其实在这种情况下ASM.js只是一个备用方案,和其他备用方案一样通常都比较明显的慢于目标实现。下面是一个将Rust解析器作为ASM.js模块运行的基准测试:

test

Javascript parser (ms)

Rust parser as an ASM.js module (ms)

speedup

demo-post.html

15.368

2.718

× 6

shortcode-shortcomings.html

31.022

8.004

× 4

redesigning-chrome-desktop.html

106.416

19.223

× 6

web-at-maximum-fps.html

82.92

27.197

× 3

early-adopting-the-future.html

119.880

38.321

× 3

pygmalian-raw-html.html

349.075

23.656

× 15

moby-dick-parsed.html

2,543.75

361.423

× 7

ASM.js模块版本的Rust解析器比纯Javascript实现平均快6倍。中位数也是6倍。这和WebAssembly版本比较还是有较大差距,但是考虑到这是个备用方案,而且平均已经快了6倍了,已经很不错了!

因此不仅是整个工作流因为Rust而变得更加安全,而且得到的结果也比Javascript快。

在这个系列的后续文章中我们将会看到Rust会到达很多的星系,Rust越多的往后旅行,也会变得更加有趣。

谢谢阅读!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档