📄 Page
1
(This page has no text content)
📄 Page
3
版权信息 书名:WebAssembly实战 作者:[加] C. 杰勒德 • 加伦特(C. Gerard Gallant) 译者:单业 ISBN:978-7-115-56145-9 本书由北京图灵文化发展有限公司发行数字版。版权所有,侵权必 究。 您购买的图灵电子书仅供您个人使用,未经授权,不得以任何方式复 制和传播本书内容。 我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产 权。 如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该 帐号等维权措施,并可能追究法律责任。
📄 Page
4
版权声明 Original English language edition, entitled WebAssembly in Action by C. Gerard Gallant, published by Manning Publications. 178 South Hill Drive, Westampton, NJ 08060 USA. Copyright © 2019 by Manning Publications. Simplified Chinese-language edition copyright © 2021 by Posts & Telecom Press. All rights reserved. 本书中文简体字版由Manning Publications授权人民邮电出版社独家 出版。未经出版者事先书面许可,不得以任何方式或途径复制或传播 本书内容。 版权所有,侵权必究。
📄 Page
5
前言 与一些朋友相比,我在编程方面开窍较晚。直到高中时,我才偶然接 触编程。当时我需要再修一门计算机课程,辅导老师向我推荐了一门 选修课。我以为要学习的是计算机工作原理,但出乎意料,这门课程 是关于编程的。很快我就对它痴迷不已,并将职业发展方向从建筑学 调整为软件架构。 2001年,我加入了Dovico软件公司,工作内容是维护与改进公司的 C++客户端/服务器应用程序。当时的风向已经转变,2004年,Dovico 决定转向软件即服务模式,我也将工作重心转到了Web应用程序产品。 虽然我仍会帮助维护C++应用程序,但我的核心关注点变成了用C#和 JavaScript进行Web开发。目前,我仍然从事Web开发,但是重心转到 了体系结构方面——构建API、使用数据库,以及探索新技术。 我很乐于通过博客和公开演讲回馈开发者社区。2017年9月,有人问我 是否有兴趣为本地用户做一场演讲。在四处浏览以寻找演讲主题时, 我看到了一篇来自于PSPDFKit的文章,其中讨论了一种名为 WebAssembly的技术。 当时我已经对谷歌的Native Client(PNaCI)技术有所了解,它允许 编译后的C/C++代码在Chrome浏览器中以接近原生的速度运行。我也了 解过Mozilla的asm.js技术,借助这种技术,可以将C/C++代码编译为 JavaScript的一个子集,并让它在支持此技术的浏览器中高速运行。 它也可以在不支持asm.js的浏览器中运行,只不过速度上没有优势, 因为它就是JavaScript。这是我第一次听说WebAssembly。 WebAssembly拥有asm.js的优点,同时致力于弥补其缺点。有了 WebAssembly,你能够以多种语言编写代码,并将它编译为可在浏览器 中安全运行的代码,而且它在所有主流的桌面浏览器与移动端浏览器 中都可用!它也可以应用于浏览器之外,如Node.js!我被 WebAssembly的潜力深深触动,从那时起,便开始利用所有空闲时间探 索这项技术,并撰写与之相关的博客文章。
📄 Page
6
2017年年末,Manning出版社注意到了我的博客文章,相关人员问我是 否有兴趣撰写一本关于WebAssembly的图书。最开始,我计划在书中覆 盖多门语言,并分别从后端开发者和前端开发者的角度来展示如何使 用这项技术。但是,初稿的讲解重点明显不够突出,因此我和审校人 决定将范围收窄,只关注C/C++语言,并且侧重于后端开发者。 在我撰写本书时,WebAssembly社区与工作组也没有闲着。实际上,这 项技术的几项改进正在进行当中。最近,不需要启用任何实验性功能 就可以在Chrome的桌面版本中使用多线程WebAssembly模块了! WebAssembly有潜力帮助Web开发更上一层楼,对于它的未来,我拭目 以待。
📄 Page
7
致谢 有人告诉过我写书是一件耗时费力的事情,但我没想到是如此耗时费 力!本书得到了各位编辑和审稿人的帮助,也得到了购买本书早期版 本的读者的反馈,所以我确信它是一本很棒的图书,可以帮助你上手 使用WebAssembly技术。 我需要感谢很多人,他们使本书的出版成为可能。首先,感谢我的家 人,当我在深夜、周末以及假期加班,甚至为了赶上截稿日期而占用 休假时间时,他们给予了我充分的理解。感谢我的妻子Selena以及女 儿Donna和Audrey,我爱你们! 接下来,我要感谢在Manning遇到的第一位编辑Kevin Harreld,他帮 助我起步并着手撰写本书。后来Kevin去了另一家公司工作,让我有机 会与Toni Arritola愉快地合作完成本书的剩余部分。Toni,谢谢你与 我一起工作时的耐心,谢谢你的专业精神、实事求是,以及对高质量 的追求。 感谢Manning参与本书从市场营销到出版发行这一过程的每一位工作人 员。感谢你们不懈的努力。 感谢在本书的各个阶段从百忙之中抽出时间阅读本书并给出建设性反 馈的审稿人,包括Christoffer Fink、Daniel Budden、Darko Bozhinovski、Dave Cutler、Denis Kreis、German Gonzalez- Morris、James Dietrich、James Haring、Jan Kroken、Jason Hales、Javier Muñoz、Jeremy Lange、Jim Karabatsos、Kate Meyer、Marco Massenzio、Mike Rourke、Milorad Imbra、Pavlo Hodysh、Peter Hampton、Reza Zeinali、Ronald Borman、Sam Zaydel、Sander Zegveld、Satej Kumar Sahu、Thomas Overby Hansen、Tiklu Ganguly、Timothy R. Kane、Tischliar Ronald、 Kumar S. Unnikrishnan、Viktor Bek以及Wayne Mather。 特别要感谢我的技术编辑Ian Lovell,他在整个过程中提出了大量宝 贵的意见。同时要感谢我的技术校对Arno Bastenhof,在本书出版 前,他对代码进行了最终的审查。
📄 Page
8
最后,非常感谢各浏览器厂商,他们合作创造了一项未来若干年Web都 会从中受益的技术。感谢世界各地持续改进WebAssembly并扩展其使用 范围的人们。这项技术的潜力是巨大的,我已经迫不及待想要看到 WebAssembly会将我们带向何处。
📄 Page
9
关于本书 本书旨在让你了解什么是WebAssembly、它的工作原理,以及用它能够 做什么、不能做什么。你将了解如何根据需要创建WebAssembly模块。 本书将从简单示例开始,然后逐步深入高级主题,如动态链接、并行 处理以及调试。 目标读者 本书的目标读者是对C或C++、JavaScript和HTML有基本理解的开发 者。尽管互联网上有在线的WebAssembly资源,但其中一些已经过时, 而且通常并未深入细节或涉及高级主题。本书将以易于学习的形式呈 现各种信息,初学者和专家开发者都可以从中受益,从而创建 WebAssembly模块并与之交互。 内容结构 本书共有13章,分为4个部分。 第一部分解释了WebAssembly是什么及其工作原理。这一部分也介绍了 Emscripten工具包,全书都将用其来创建WebAssembly模块。 第1章讨论了WebAssembly是什么、它能解决什么问题,以及它的 工作原理,解释了WebAssembly的安全性来源、可用于创建 WebAssembly模块的语言,以及使用这些模块的场合。 第2章阐释了WebAssembly模块的组织结构,以及模块每一部分的 职责。 第3章介绍了Emscripten工具包,讲解了创建WebAssembly模块时 可用的不同输出选项,还介绍了WebAssembly JavaScript API。 第二部分带领你创建一个WebAssembly模块并在Web浏览器中与之交 互。
📄 Page
10
第4章讲解了如何调整已有的C或C++代码库,使其也可以编译为 WebAssembly模块。你还将学习为网页编写JavaScript代码,从而 与这个模块交互。 第5章讲解了如何调整第4章中创建的代码,使得这个WebAssembly 模块可以调用网页的JavaScript代码。 第6章带领你修改WebAssembly模块,让它可以兼容从JavaScript 代码传入的函数指针。这允许JavaScript代码按需指定函数,并 使用JavaScript promise。 第三部分介绍了几个高级主题,如动态链接、并行处理,以及如何在 非Web浏览器环境中操作WebAssembly模块。 第7章介绍了动态链接的基础知识。两个或多个WebAssembly模块 可以在运行时通过动态链接合而为一。 第8章扩展了第7章所学,介绍如何创建同一个WebAssembly模块的 多个实例,并将每个实例按需动态链接到另一个WebAssembly模 块。 第9章讲解了Web worker和pthread。在这一章中,你可以学到如 何使用Web worker在浏览器的一个后台线程中按需预取 WebAssembly模块,以及如何在WebAssembly模块中用pthread线程 执行并行处理。 第10章展示了WebAssembly并不局限于Web浏览器。你将学习如何 在Node.js中使用自己的若干WebAssembly模块。 第四部分深入探讨了调试和测试。 第11章通过构建一个卡牌匹配游戏,讲解了WebAssembly文本格 式。 第12章扩展了这个卡牌匹配游戏,以展示调试WebAssembly模块时 可用的各种选项。 第13章讲解了如何为自己的模块编写集成测试。 每一章都建立在前面章节的内容之上,因此最好按顺序阅读。开发者 应该依次阅读第1~3章,以理解WebAssembly是什么、它的工作原理, 以及如何使用Emscripten工具包。附录A很重要,你可以利用它来正确 设置工具,以跟随本书代码。本书前两部分覆盖了核心概念,其余部 分(高级主题和调试主题)可以根据需要阅读。
📄 Page
11
关于代码 本书包含很多源代码示例,有编号列表形式的,也有嵌入正文之中 的。为了区分代码与普通文本,代码以等宽字体表示。另外,如果代 码是从前面的示例修改而来,则修改部分以黑体表示。 有时本书展示的代码会换行和缩进以适应页面空间。极少数情况下, 如果仍然没有足够的空间,那么会使用续行符(➥)。 可以从Manning出版社网站获得本书源代码,参见 www.manning.com/books/webassembly-in-action。 读者也可到图灵社区本书中文版主页“随书下载”处下载书中示例源代码。——编者注 本书论坛 购买本书即可免费访问Manning出版社维护的一个私有Web论坛,你可 以在该论坛上评论本书、提出技术问题,也可以从作者和其他用户那 里获得帮助。 Manning出版社为所有读者提供了一个交流场所,以便读者之间以及读 者和作者之间可以进行有意义的对话。论坛并不能确保作者的参与程 度,因为他对论坛的贡献仍然是自愿性质的(且是无偿的)。建议你 尝试向作者询问一些具有挑战性的问题,以引起对方的兴趣。只要书 仍然在版,你就可以在出版社的网站上访问该论坛和相关讨论。 读者也可登录图灵社区本书中文版主页,提交反馈意见和勘误。——编者注 其他在线资源 如需更多帮助,可访问以下网站和社区。 Emscripten官网为许多任务提供了大量文档。 Emscripten社区非常活跃,发布频繁。如果发现了Emscripten本 身的问题,你可以查看是否已经有人提交了bug报告,或者了解如 1 1 2 2
📄 Page
12
何解决你所遇到的问题。 Stack Overflow也是一个很棒的网站,你可以在这里提问或帮助 他人。 本书中文版网址链接请到图灵社区本书页面查看。 更多信息 扫描下方二维码,即可获取电子书相关信息及读者群通道入口。
📄 Page
13
关于封面图片 本书封面图片名为“Fille Lipparotte”,即“Lipparotte家的女 孩”。插图取自Jacques Grasset de Saint-Sauveur(1757—1810) 所著的各国服饰集,名为Costumes civils actuels de tous les peuples connus(1788年在法国出版)。其中每幅插图均为手工绘制 和上色。这本图集中服饰的多样性,能让我们想起200年前世界上的城 镇和地区在文化上有多么大的差异。彼此隔离的人们说着不同的语言 和方言。在街道与乡野间,仅从他们的衣着就能轻易地辨别出他们的 居住场所,以及从事的行业或生活状况。 从那时起,我们的穿衣方式发生了变化,一度丰富的地区多样性也逐 渐消失了。现在已经很难区分不同地区的居民,更不用说不同城镇、 区域或国家的居民了。也许我们已经用文化的多样性换取了更多样的 个人生活——当然换取的是更多样的、快节奏的技术生活。 当我们很难分辨一本计算机图书和另一本计算机图书的时候,Manning 以Grasset de Saint-Sauveur这套两个世纪前丰富多样的地区生活为 基础的图书封面,将计算机行业的创造性和主动性表现得淋漓尽致。
📄 Page
14
第一部分 起步 这一部分将介绍WebAssembly,以及创建WebAssembly模块的过程。 第1章将介绍WebAssembly是什么、它所解决的问题、它的安全性来自 何处,以及可以使用哪些编程语言创建WebAssembly模块。 第2章将介绍WebAssembly模块的内部结构,以便你了解每个部分的用 途。 第3章将用Emscripten工具包创建第一个WebAssembly模块,以便学习 可用的输出选项。这一章还会介绍WebAssembly JavaScript API。
📄 Page
15
第 1 章 初识WebAssembly 本章内容 WebAssembly是什么 WebAssembly能解决什么问题 WebAssembly的工作原理 WebAssembly的安全性来自何处 哪些语言可用于创建WebAssembly模块 提到Web开发,大多数开发者最关心的一件事就是性能——从网页加载 速度到整体的响应性。若干研究表明,如果网页不能在3秒内完成加 载,那么40%的访问者就会离开。这个百分比会随着网页加载秒数的增 加而增加。 网页加载时间并不是唯一的问题。根据一篇谷歌论文所述,如果某个 网页的性能很差,那么会有79%的访问者声称他们不太可能再次访问该 网站(参见Daniel An与Pat Meenan于2016年7月合著的文章“Why marketers should care about mobile page speed?”)。 随着Web技术的发展,越来越多的应用程序转移到Web上。这就向开发 者提出了另一项挑战,因为Web浏览器只支持一种编程语言: JavaScript。 从某种意义上说,在所有浏览器上只使用一种编程语言是好的——只 需要编写代码一次,就可以确保它能在所有浏览器上运行。但仍需要 在想要支持的每个浏览器上测试代码,因为有时各个厂商的实现方式 会略有不同。另外,有时一个浏览器厂商并不与其他厂商同时添加某 项新功能。总体上说,只支持一种语言比支持四五种语言简单一些。 但浏览器只支持JavaScript的缺点是,我们想要移植到Web上的应用程 序并不是用JavaScript编写的,而是用C++这样的语言编写的。 JavaScript是一种很棒的编程语言,但现在我们要求它做的已经超出 其本来的设计意图(比如游戏所需要的密集型计算),并且还要求它 能够快速执行。
📄 Page
16
1.1 WebAssembly是什么 随着浏览器开发商寻找提高JavaScript性能的方法, Mozilla(Firefox浏览器开发者)定义了一个名为asm.js的 JavaScript子集。 1.1.1 WebAssembly的先驱:asm.js asm.js具有以下优势。 你并不直接编写asm.js,而是用C或C++编写逻辑,然后将其转换 为JavaScript。将代码从一种语言转换到另一种语言的过程称为 transpiling。 大计算量代码可以更快地执行。当浏览器的JavaScript引擎看到 名为asm pragma语句的特殊字符串("use asm";)时,其作 用相当于一个标记,以此告诉浏览器它可以使用底层系统操作而 不是更昂贵的JavaScript操作。 从第一次调用就可以获得更快的代码执行速度。包含的类型提示 可以告知JavaScript一个变量会持有何种类型的数据。比如,可 以用a|0来提示变量a将持有一个32位整型值。这种方法很有效, 因为0的位OR运算不会改变原始值,所以这么做不会产生副作用。 这些类型提示作为一个承诺向JavaScript引擎表明,如果代码将 一个变量声明为整型,那么它永远不会被修改,比如不会改为字 符串。因此,JavaScript引擎不需要监测代码来确定这些类型, 而是可以像代码所声明的那样直接编译它。 以下代码片段展示了一个asm.js代码示例。 function AsmModule() { "use asm"; ←---- 用于通知JavaScript后续代码为asm.js的标记 return { add: function(a, b) { a = a | 0; ←---- 类型提示表明参数为32位整型 b = b | 0; return (a + b) | 0; ←---- 类型提示表明返回值为32位整型 }
📄 Page
17
} } 虽然具有以上优点,但asm.js仍有一些缺点。 所有这些类型提示可能会使文件非常大。 因为asm.js文件是JavaScript文件,所以它仍然需要由 JavaScript引擎读入和解析。在像手机这样的设备上,这会是个 问题,因为所有处理过程延长了加载时间,而且会消耗电量。 为了添加新特性,浏览器厂商将不得不修改JavaScript语言本 身,而这并不是我们所期望发生的。 JavaScript是一种编程语言,并没有设计用来作为编译目标。 1.1.2 从asm.js到MVP 浏览器厂商关注改进asm.js的方法,他们想出了一个WebAssembly最小 化可行产品(minimum viable product,MVP),其目标是保留asm.js 的优点,同时解决其缺点。2017年,4个主流浏览器厂商(谷歌、微 软、Apple和Mozilla)都更新了自己的浏览器,以提供对此MVP(有时 也称为Wasm)的支持。 WebAssembly是一种底层类汇编语言,能够在所有当代桌面浏览器 及很多移动浏览器上以接近本地的速度运行。 WebAssembly文件设计得很紧凑,因此可以快速传输和下载。这些 文件的设计方式也使得它们可以快速解析和初始化。 WebAssembly被设计为编译目标,因此用C++、Rust和其他语言编 写的代码现在可以在Web上运行了。 后端开发者可以利用WebAssembly来提高代码复用度或者无须重写就将 自己的代码移植到Web中。Web开发者也可以从创建新库、改进现有 库,以及提高自己代码中大计算量部分的性能中获益。尽管 WebAssembly主要用于Web浏览器,但其设计也考虑到了可移植性,因 此也可以在浏览器之外使用。 1.2 WebAssembly解决了哪些问题 WebAssembly MVP解决了asm.js的以下问题。
📄 Page
18
1.2.1 性能改进 WebAssembly致力于解决的最大问题之一是性能问题——从代码的下载 时间到代码的执行速度。使用编程语言,而不是编写计算机处理器理 解的机器语言(1和0,或者本地代码)时,你通常会编写更接近于人 类语言的某种东西。尽管使用从计算机细节中抽象出来的代码更容 易,但计算机处理器并不理解你的代码,因此运行时需要将你编写的 内容转换为机器码。 JavaScript是一种解释型编程语言,也就是说,它会在执行时读入你 编写的代码,并将这些指令即时翻译为机器码。使用解释型语言时, 不需要提前编译代码,这意味着它启动的速度更快。但缺点是,解释 器必须在每次运行代码时将指令转换为机器码。举例来说,如果你的 代码在执行一个循环,那么每次执行该循环时,循环的每一行都要被 解释。因为解释过程并不总有大量时间可用,所以并不总能进行优 化。 其他编程语言(如C++)并不是解释型的。使用这类语言时,需要利用 称为编译器的特定程序预先将指令转换为机器码。使用编译型编程语 言时,需要一些时间将指令转换为机器码,然后才能运行它们,但其 优点是有更多时间来优化代码的执行;一旦指令编译为机器码,就不 需要再次编译。 随着时间的发展,JavaScript已经从简单连接多个组件的胶水语言 (那时预计其生存期很短)发展为大量网站用于执行复杂处理的语 言,它很容易涉及成百上千行代码,而且,随着单页应用程序的兴 起,其代码通常生存期很长。互联网已经从只是展示文本和少量图片 的网站发展为具有强交互性的网站,甚至是称为Web应用程序的站点, 因为它们类似于桌面应用程序,只不过运行于Web浏览器中。 随着开发者持续挑战JavaScript的极限,一些引人注意的性能问题开 始显现出来。浏览器厂商决定要找到一个折中点,不仅可以获得解释 器的优点,代码被调用时能够尽快启动,而且拥有执行时能更快速运 行的代码。为了让代码更快,浏览器厂商引入了一个称为JIT(just- in-time,即时)编译的概念,JavaScript引擎在运行时监测代码。如 果某一部分代码被使用的次数足够多,那么引擎就会试图将这一部分
📄 Page
19
编译为机器码,这样它就可以绕过JavaScript引擎,转而使用底层系 统方法,这要快得多。 JavaScript引擎需要监测代码多次才能将其编译为机器码,因为 JavaScript也是一种动态编程语言。在JavaScript中,一个变量可以 持有任何类型的值。举例来说,一个变量可能在最初持有一个整型 值,但之后被赋予一个字符串。代码运行若干次之后,浏览器才能了 解应该预期什么(类型)。即使是在编译后,也仍然需要监测这段代 码,因为某些条件可能会改变,此时需要抛弃这部分编译后的代码, 重新开始整个处理过程。 1.2.2 比JavaScript更快的启动速度 和asm.js一样,WebAssembly不是设计用于手动编写的,也不是供人类 阅读的。代码被编译为WebAssembly之后,字节码会以二进制格式而不 是文本格式表示,这可以减小文件大小,从而支持快速传输和下载。 这个二进制文件的设计方式使得模块验证可以在一轮内完成,其结构 也支持并行编译文件的不同部分。 通过实现JIT编译,浏览器厂商在提高JavaScript性能方面获得了巨大 进步。但是JavaScript引擎只能在监测代码若干次后才将其编译为机 器码。另外,WebAssembly代码是静态类型的,即可以预知变量持有的 值的类型。WebAssembly代码可以从一开始就编译为机器码,无须先监 测,因此第一次运行代码就可以看到性能提升。 自MVP首次发布以来,浏览器厂商已经通过不同方式提升WebAssembly 的性能。其中一种方式是引入了一种称为流编译的技术,在浏览器下 载和接收文件时,该技术可以将WebAssembly代码编译为机器码。流编 译支持WebAssembly模块下载完毕即进行初始化,这样会显著加速模块 的启动过程。 1.2.3 可以在浏览器中使用JavaScript之外的语言 目前为止,要想在Web上运行非JavaScript语言,需要将代码转换为 JavaScript,但后者并未被设计为编译目标。而WebAssembly从一开始
📄 Page
20
就被设计为编译目标,因此,如果开发者想要使用某种特定的语言进 行Web开发,无须将代码转译为JavaScript就可以实现。 因为WebAssembly没有绑定到JavaScript语言,所以这项技术更容易改 进,而无须担心会影响JavaScript。这种独立性促使WebAssembly具备 大幅提升性能的能力。 对于WebAssembly MVP来说,C和C++是目标为WebAssembly的重点语 言,但Rust也对此增加了支持,并且几种其他语言也在试验对它的支 持。 1.2.4 代码复用的机会 能够用非JavaScript语言编写代码,并将其编译为WebAssembly,对于 代码复用来说,这为开发者提供了更多灵活性。过去不得不用 JavaScript重写的东西现在可以直接在桌面或服务器上使用,并在浏 览器中运行。 1.3 WebAssembly的工作原理 如图1-1所示,使用JavaScript时,代码被包含在网站中并在运行时解 释。因为JavaScript变量是动态的,所以观察图示中的函数add可以 发现,当前处理的变量为何种类型并不是显而易见的。变量a和b可能 是整型、浮点型、字符串,甚至是它们的组合,比如一个变量是字符 串,而另一个是浮点型。