【JSConf EU 2018】WebAssembly 的手工艺术

简介: 在今年欧洲的JSConf上Emil Bay进行了一场题为《Hand-Crafting WebAssembly》的演讲。Emil表示:“现在已经有很多关于WebAssembly(WASM)的演讲。遗憾的是,大多数演讲是关于如何把高级语言编译成wasm的,他们把wasm当成一个半透明的盒子。

在今年欧洲的JSConf上Emil Bay进行了一场题为《Hand-Crafting WebAssembly》的演讲。Emil表示:“现在已经有很多关于WebAssembly(WASM)的演讲。遗憾的是,大多数演讲是关于如何把高级语言编译成wasm的,他们把wasm当成一个半透明的盒子。WebAssembly是一门有趣的语言,你可以用它写出性能低于C的代码”。在这此的演讲中,Emil向我们演示了如何写WAT(WebAssembly的文本格式)以及当拥有大内存时,如何推理算法,如何将高级结构(如循环)转换为基础指令,同时获得乐趣!Emil演示了如何把一些难度逐渐递增的算法转换成基础指令,在没有抽象的情况下每一个算法的实现都充满着挑战。即时你在工作中并没有使用WASM,学习计算机的最低级指令可以拨开抽象的迷雾,揭示计算机的神奇。在开始正文之前让我们先一睹大佬风采

什么是WebAssembly

“WebAssembly(缩写Wasm)是运行在一个基于栈的虚拟机上的二进制指令格式。Wasm是为了把像C/C++/Rust等高级语言编译成便携式的目标而设计的,可以被部署到Web端和服务端应用”。 这是WebAssembly官网的解释,听起来不错,但是今天我们可以忘记这些,因为我们今天用不到这些高深的技术术语。通过“WebAssembly”这个单词你可能猜想它运行在浏览器端的汇编语言。实际上,它既不是很Web,也不是很Assembly(Not very Web, not very Assembly)。

为什么这么说WebAssembly “Not very Web, not very Assembly”呢?

  • 它不能直接使用Web API。
  • WebAssembly代码不是直接运行在物理机上的,虽然它很接近物理机,但它仍然是一个抽象出来的运行环境。
  • 不能系统调用,除非你通过JavaScript给它调用通道。
  • 不能使用新的硬件设备。例如:蓝牙。
  • 没什么魔法,只是计算。

吐槽了那么多,到底WebAssembly是什么呢?

  • 64位整型(i64) WebAssembly最让我兴奋的的是它可以使用64位的整型数字,这让我们可以精确的描述那些需要数字计算的事物。由于我的工作是关于密码学的,我们经常需要处理256位或者512位长度的二进制数字,64位整型数字的支持对性能提升确实很有效。

  • 性能提升(Performance Boost) 人们通常通过把代码转换成WebAssembly来获得性能的提升,但是根据我的经验通常收益不像想象的那么大。我通过以前一些实验得出WebAssembly相对JavaScript性能大约提升了20%至30%。因为JavaScript在一些新的JavaScript引擎(v8、SipderMonkey等)上已经运行的很快了!

  • 精度/可预测性(Precision/Predictable) 使用JavaScript写代码的时候,你通常不知道写出来的代码性能怎么样,除非你研究过底层的虚拟机。使用WebAssembly你更接近代码的底层运行,所以代码的表现或性能将更加可预测。

  • Run anywhere 另一件,让人感到兴奋的的事是WebAssembly可能在不久以后成为唯一一个可以跨平台、跨端运行的语言。我已经看到有人在使用WebAssembly写Linux内核的项目,还有人在浏览器里加载WebAssembly模块。

WebAssembly不是什么未来的黑科技,现在丹麦已经有超过77%的浏览器支持,而全球也已经有超过73%的浏览器支持,而且Node.js 8.0以上也支持WebAssembly,所以你现在就可以使用它。

WebAssembly Text-format

下面我们要手撸WebAssembly,而不是通过高级程序语言编译成WebAssembly。 WebAssembly是一种二进制格式的低级(low level)类汇编语言,官方为了让人类能够阅读和编辑它,还提供了相应的文本格式(wat)。

1. 平方运算

从一个简单的平方计算的函数开始我们的第一个WebAssembly模块:


 $square
        (export "square")
        (param $x i32)
        (result i32)
        (return (i32.mul (get_local $x)
                         (get_local $x)))))


这里我们定义了一个平方运算的函数square,它接受一个你i32类型的参数,返回结果也是i32。通过这个模块我们应该注意到以下几点:

  • wat文本采用的是S-expressions的语法(类似LISP)。
  • 模块是WebAssembly的基本单位,这点和ES6的模块很像。
  • 标签(参数名、变量名和函数名)使用 $ 前缀声明。
  • 明确的类型,参数、变量、函数返回都有类型声明。
  • 运算操作是通过type.op形式的指令调用,type代表运算结果的类型,op是要做的运算操作。如:i32.mul表示要做乘法运算(mul),运算操作的结果的类型是i32(32位整型数字)。
  • 显示访问,当要使用一个变量时,我们需要显示访问。如:get_local $x,我们使用get_local显示访问了本地变量x

我们来看下这个模块是如何使用的?

  1. 将上面的“First module”保存到square.wat文件。你也可以从handcrafting-webassembly这个仓库直接克隆获取源码。

  2. 安装编译工具wabtwat2js

$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ make  #cmake, git, make required
$ npm i -g wat2js

安装完成后将 /wabt/bin目录添加到系统的环境。mac是添加到*/etc/paths*文件。

3.生成wasm模块和JavaScript胶水代码文件

$ wat2wasm square.wat  #生成square.wasm文件
$ wat2js square.wat -o square.js  #生成加载wasm模块的CommonJS模块

4.使用wasm模块。新建
example.js,添加如下代码:

var wasm = require('./square.js');
console.log(wasm.exports.square(2));  // 4 

通过这个简单的WebAssembly小模块,我们应该已经掌握了WebAssembly文本格式一些基本语法以及如何使用它。接下来我们来看下Emil在实际工作中写的代码。

2. 计算两点之间距离

下面的这段代码定义并导出了一个f64.distance的函数,它接受四个参数分别是x1、y1、x2、y2,返回一个64位浮点型数字。这段代码还是比较好理解的,有了之前的“First Module”的经验你应该已经知道如何使用它。同样,你可以在handcrafting-webassembly找到它的源码。


(module 
    (func $square
        (export "square")
        (param $x i32)
        (result i32)
        (return (i32.mul (get_local $x)
                         (get_local $x))))

    (func $f64.distance
        (export "f64.distance")
        (param $x1 i32) (param $y1 i32)
        (param $x2 i32) (param $y2 i32)
        (result f64)
        
        (local $x.dist i32)
        (local $y.dist i32)
        
        (set_local $x.dist (i32.sub (get_local $x1)
                                    (get_local $x2)))
        
        (set_local $y.dist (i32.sub (get_local $y1)
                                    (get_local $y2)))
        
        (return (f64.sqrt (f64.convert_u/i32 (i32.add (call $square (get_local $x.dist))
                                   (call $square (get_local $y.dist)))))))
                                   
)

让我们把难度再提升一个等级。

3. 计算矢量间的距离

矢量间距离计算,其实相当于两个数组间距离的计算。这段代码的难度就增加了很多!这里用到了WebAssembly的线性内存(Linear memory)和loop指令。

  • memory是WebAssembly的一个重要的概念,它是用来实现JavaScript和WebAssebly模块间通信的,本质上就是一个大的共享数组。下面的模块中,我们创建并导出了一页(64KiB)大小的momery实例。导出的memory实例是提供给JavaScript使用。通过JavaScript把外部数组的数据存到memory,然后我们可以WebAssebly模块里访问它。
  • loop指令用来定义循环代码块。紧跟在loop指令后面需要定义一个标签,在这里我们定义的是“continue”。WebAssembly的循环和JavaScript有些不同。在JavaScript的循环里有continue和break两个分支。WebAssembly的循环比较像do-while循环,但它只有一个条件分支,当br_if条件为真的时候,继续执行指定标签的循环。
(module 
    (memory (export "memory") 1)

    (func $i8.distance
        (export "i8.distance")
        (param $v.ptr i32)
        (param $w.ptr i32)
        (param $len i32)
        (result f64)
        
        (local $distance.sq i32)
        (local $elm i32)
        (local $offset i32)
        
        (set_local $distance.sq (i32.const 0))
        
        (loop $continue
            ;; $elm = $w[$offset] - $v[$offset]
            (set_local $elm (i32.sub (i32.load8_u (i32.add (get_local $w.ptr)
                                                           (get_local $offset)))
                                     (i32.load8_u (i32.add (get_local $v.ptr)
                                                           (get_local $offset)))))
            ;; $distance.sq += $elm ** 2
            (set_local $distance.sq (i32.add (get_local $distance.sq)
                                             (i32.mul (get_local $elm)
                                                      (get_local $elm))))
            ;; $offset++ < $len ? continue : break
            (br_if $continue (i32.lt_u (tee_local $offset 
                                                 (i32.add (get_local $offset)
                                                          (i32.const 4))) ;; bytewidth of i32
                                       (get_local $len))))

    (return (f64.sqrt (f64.convert_u/i32 (get_local $distance.sq))))))


通过这个例子,我们来看下数字在memory里面是如何存储的。如下图,我们可以看到i8类型表示的是八个比特位(bit)整型数字,也就是一个字节(byte)。i32表示的是四个字节长度的整型数字,f64表示的是八个字节的浮点型数字。所以说memory其实就是一个字节数组。在JavaScript里面数字只有Number类型,我们也不需要关心数字在内存中是如何存储的,但是在WebAssembly里,你必须知道如何为一个数字分配合适它的内存(定义合适的类型)。

那我们是如何解析数组的呢?答案是通过指针(pointer)和数组的长度(length)。在这里指针也就相当于数组的下标(index),长度也就是数组分配的内存大小。

总结

通过手撸三个难度递增的的WebAssembly模块,对于理解WebAssembly在内存使用和运行机制应该有所收益。但是还是要提醒大家手撸WebAssembly并不符合它的设计初衷。演讲的最后阶段,Emil还介绍了自己加密算法库sodium-nativesodium-universal(广告时间 啊哈~),如果你感兴趣的话可以移步到他的gayhub。(完

原文发布时间为:2018年06月29日
本文作者: leyayun
本文来源:掘金  如需转载请联系原作者

相关文章
|
2天前
|
人工智能 自然语言处理 自动驾驶
SAM 2.1上新、Lingua代码库发布,一大波Meta开源工具来袭
Meta(原Facebook)近期发布了多项重要更新,包括SAM 2.1图像分割模型的升级和Lingua多语言处理代码库的发布。SAM 2.1在处理复杂场景和细节上表现更佳,提供更精细、快速且广泛适用的分割效果。Lingua则支持多种语言处理,具备丰富的工具集和易于集成的特点,为开发多语言AI应用提供了强大支持。这些工具不仅提升了开发者的灵活性和效率,也促进了AI领域的知识共享与创新。
7 1
|
2月前
|
资源调度 前端开发 JavaScript
PostCSS的安装与应用技术全解
PostCSS通过其插件系统提供了极大的灵活性和强大的CSS处理能力。无论是自动化添加浏览器前缀、使用未来的CSS特性还是优化CSS文件大小,PostCSS都能在现代前端开发流程中发挥重要作用。通过合理配置和使用,它将大大提升你的工作效率及项目质量。
30 4
|
3月前
|
Rust 前端开发 JavaScript
震惊!JavaScript 与 WebAssembly 强强联合,开启前端性能传奇之旅,你准备好了吗?
【8月更文挑战第27天】在互联网飞速发展的今天,前端技术,特别是核心语言JavaScript,正经历着持续的革新。为了突破JavaScript在处理复杂计算时的性能局限,WebAssembly应运而生。作为一种高效的二进制格式,WebAssembly能以接近原生的速度在浏览器中运行,支持C、C++和Rust等语言编写的高性能代码。它与JavaScript相辅相成,前者专注于高性能计算任务(如游戏开发、图像处理),后者则负责页面的交互与逻辑控制。通过结合使用,二者为前端开发者提供了更为强大和灵活的工具集,共同推动前端技术进入一个全新的性能时代。
92 2
|
3月前
|
前端开发 JavaScript C++
【绝技大公开】Webpack VS Rollup:一场前端工程化领域的巅峰对决,谁能笑到最后?——揭秘两大构建神器背后的秘密与奇迹!
【8月更文挑战第12天】随着前端技术的发展,模块化与自动化构建成为标准实践。Webpack与Rollup作为主流构建工具,各具特色。Webpack是一款全能型打包器,能处理多种静态资源,配置灵活,适合复杂项目;Rollup专注于ES6模块打包,利用Tree Shaking技术减少冗余,生成更精简的代码。Rollup构建速度快,配置简洁,而Webpack则拥有更丰富的插件生态系统。选择合适的工具需根据项目需求和个人偏好决定。两者都能有效提升前端工程化水平,助力高质量应用开发。
41 1
|
3月前
|
JavaScript 前端开发 应用服务中间件
Vue.js项目部署与优化:一场从本地到生产环境的华丽蜕变,见证你的应用如何凤凰涅槃,惊艳上线!
【8月更文挑战第30天】作为一名前端开发者,掌握从本地开发环境到生产环境的迁移至关重要。本文将带你了解如何使用 Vue.js 构建和打包应用,确保其在生产环境中流畅运行。首先,通过 `npm run build` 或 `yarn build` 命令生成生产环境文件;接着,配置服务器(如 Nginx)以支持静态文件服务;最后,通过代码分割、资源压缩、CDN 使用、服务端渲染及缓存策略等手段优化应用性能。跟随本文,你将学会如何让 Vue.js 应用在真实环境中表现优异,为用户提供流畅体验。
52 0
|
3月前
|
开发者 前端开发 Apache
【绝不错过!】揭秘Wicket大神级插件,带你飞越编程极限,探索Web应用开发新大陆!
【8月更文挑战第31天】Apache Wicket是一个成熟的Java Web框架,以其组件化体系结构、直观的API及对MVC的支持著称。其活跃社区贡献了大量插件和扩展,显著提升了Wicket的功能性。本文推荐几个实用插件,如**Wicket Ajax Support**,可轻松添加Ajax功能,提升用户体验;**Bootstrap for Wicket**则将Bootstrap与Wicket结合,美化应用界面。
36 0
|
4月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的河北任丘非物质文化遗产数字化传承的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的河北任丘非物质文化遗产数字化传承的详细设计和实现(源码+lw+部署文档+讲解等)
63 0
|
5月前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的河北任丘非物质文化遗产数字化传承附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的河北任丘非物质文化遗产数字化传承附带文章和源代码部署视频讲解等
65 0
|
6月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的甘肃非物质文化网站的设计与实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的甘肃非物质文化网站的设计与实现(源码+lw+部署文档+讲解等)
|
前端开发 Java 物联网
GIAC-2022sh 学习笔记 | WebAssembly在前端中的应用与展望
GIAC-2022sh 学习笔记 | WebAssembly在前端中的应用与展望
357 0
GIAC-2022sh 学习笔记 | WebAssembly在前端中的应用与展望