从Rust到远方:ASM.js星系

2024-06-23 01:08
文章标签 rust js 远方 asm 星系

本文主要是介绍从Rust到远方:ASM.js星系,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

来源: 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:

function increment(x) {x = x | 0;return (x + 1) | 0;
}

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

  • stdlib,一个带有引用到标准库API的对象

  • foreign,一个带有用户定义功能的对象(比如通过WebSocket发送一些东西)

  • heap,一个表示内存的数组(因为内存是手动管理的)

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

640?wx_fmt=png

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

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

640?wx_fmt=png

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

640?wx_fmt=png

这个篇章会非常的短,应该说是最简单的一篇。要编译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二进制,只需要运行下面的命令:

$ 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工具,如下:

$ #  转换代码, 嵌入入到一个函数.
$ 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压缩输出文件:

$ # 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输出是什么样的?

640?wx_fmt=png

我可以说非常的喜欢它!

#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模块运行的基准测试:

testJavascript parser (ms)Rust parser as an ASM.js module (ms)speedup
demo-post.html15.3682.718× 6
shortcode-shortcomings.html31.0228.004× 4
redesigning-chrome-desktop.html106.41619.223× 6
web-at-maximum-fps.html82.9227.197× 3
early-adopting-the-future.html119.88038.321× 3
pygmalian-raw-html.html349.07523.656× 15
moby-dick-parsed.html2,543.75361.423× 7

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

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

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

谢谢阅读!

这篇关于从Rust到远方:ASM.js星系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1085905

相关文章

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

Node.js 数据库 CRUD 项目示例详解(完美解决方案)

《Node.js数据库CRUD项目示例详解(完美解决方案)》:本文主要介绍Node.js数据库CRUD项目示例详解(完美解决方案),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考... 目录项目结构1. 初始化项目2. 配置数据库连接 (config/db.js)3. 创建模型 (models/

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

用js控制视频播放进度基本示例代码

《用js控制视频播放进度基本示例代码》写前端的时候,很多的时候是需要支持要网页视频播放的功能,下面这篇文章主要给大家介绍了关于用js控制视频播放进度的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言html部分:JavaScript部分:注意:总结前言在javascript中控制视频播放

Node.js net模块的使用示例

《Node.jsnet模块的使用示例》本文主要介绍了Node.jsnet模块的使用示例,net模块支持TCP通信,处理TCP连接和数据传输,具有一定的参考价值,感兴趣的可以了解一下... 目录简介引入 net 模块核心概念TCP (传输控制协议)Socket服务器TCP 服务器创建基本服务器服务器配置选项服

mac安装nvm(node.js)多版本管理实践步骤

《mac安装nvm(node.js)多版本管理实践步骤》:本文主要介绍mac安装nvm(node.js)多版本管理的相关资料,NVM是一个用于管理多个Node.js版本的命令行工具,它允许开发者在... 目录NVM功能简介MAC安装实践一、下载nvm二、安装nvm三、安装node.js总结NVM功能简介N

Rust中的注释使用解读

《Rust中的注释使用解读》本文介绍了Rust中的行注释、块注释和文档注释的使用方法,通过示例展示了如何在实际代码中应用这些注释,以提高代码的可读性和可维护性... 目录Rust 中的注释使用指南1. 行注释示例:行注释2. 块注释示例:块注释3. 文档注释示例:文档注释4. 综合示例总结Rust 中的注释

Rust格式化输出方式总结

《Rust格式化输出方式总结》Rust提供了强大的格式化输出功能,通过std::fmt模块和相关的宏来实现,主要的输出宏包括println!和format!,它们支持多种格式化占位符,如{}、{:?}... 目录Rust格式化输出方式基本的格式化输出格式化占位符Format 特性总结Rust格式化输出方式

Rust中的Drop特性之解读自动化资源清理的魔法

《Rust中的Drop特性之解读自动化资源清理的魔法》Rust通过Drop特性实现了自动清理机制,确保资源在对象超出作用域时自动释放,避免了手动管理资源时可能出现的内存泄漏或双重释放问题,智能指针如B... 目录自动清理机制:Rust 的析构函数提前释放资源:std::mem::drop android的妙

Rust中的BoxT之堆上的数据与递归类型详解

《Rust中的BoxT之堆上的数据与递归类型详解》本文介绍了Rust中的BoxT类型,包括其在堆与栈之间的内存分配,性能优势,以及如何利用BoxT来实现递归类型和处理大小未知类型,通过BoxT,Rus... 目录1. Box<T> 的基础知识1.1 堆与栈的分工1.2 性能优势2.1 递归类型的问题2.2