0

    使用 WebAssembly 将前端代码速度提升20%!

    2023.07.08 | admin | 134次围观

    大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

    高级前端进阶

    WebAssembly那些事

    无论使用的是 Chrome、Firefox、Edge 还是 Safari浏览器,代码都是由 JavaScript 引擎解释和执行的,即浏览器只运行 JavaScript。 不幸的是,JavaScript 并不总是开发者想要执行的每项任务的理想选择,这就有了 WebAssembly 的用武之地。下面是MDN上对WebAssembly的描述:

    WebAssembly or "wasm" is a new portable, size- and load-time-efficient format suitable for compilation to the web.

    Chrome>57、Edge>16、Safari>11、FireFox>52、Opera>44等浏览器都已经支持WebAssembly。WebAssembly 是一种可以在现代浏览器中运行的新型代码, 它的创建是为了能获得更好的性能优势。 它是一种更偏底层的二进制格式,体积小,因此加载和执行速度很快。 开发者也不用直接编写 WebAssembly,而是可以通过将其他高级语言编译成为WebAssembly。

    WebAssembly(汇编)通常是指类似于机器代码的人类可读语言,机器代码是处理器所理解的,即一堆数字。

    WebAssembly到机器码

    为了在处理器上运行,每种高级编程语言都会被翻译成机器代码。不同类型的处理器架构需要不同的机器代码和不同类型的汇编。

    尽管名字叫 WebAssembly,但它并不完全是一种汇编语言,因为它并不适用于任何特定的机器。 它适用于浏览器html加上时间,当开发者交付要在浏览器中执行的最终代码时,开发者并不知道代码将在哪种机器上运行,而WebAssembly可以做到开发者底层完全无感。

    实际上,WebAssembly 是一种用于概念机器的语言, 当浏览器下载 WebAssembly 代码时,它可以快速将其转换为任何机器的程序集。比如:下图展示了 WebAssembly 的格式,它具有易于阅读的文本格式特性 (.wat),而右侧的二进制表示是实际交付给浏览器运行的内容 (.wasm)。

    WebAssembly 文本和二进制格式

    WebAssembly 能够让开发者将 C、C++ 或 Rust 代码之类的东西编译成所谓的 WebAssembly 模块,同时可以将其加载到 Web 应用程序中并从 JavaScript 中调用它。但是需要声明的是:WebAssembly不是 JavaScript 的直接替代品,它的设计理念是与JavaScript一起工作。

    应用程序中的 WebAssembly 模块

    1.为什么说Web是终态

    Web的优势在于它在任何地方都有效,避免下载和安装应用程序等繁琐流程,从而实现立即交付。 它比直接在计算机上下载和运行二进制文件更安全,因为浏览器建立了安全机制,可以防止其中运行的代码干扰系统。 同时,在 Web 上共享内容也非常容易 ,通过URL即可实现,开发者还可以将Web内容托管在任何地方。

    Web是终态

    Web是应用程序可在任何设备上访问、共享的唯一真正通用的平台,从而允许开发者维护一个单一的代码库,同时保持更新迅速。

    2.为什么需要 WebAssembly

    比如一些特定的场景:视频游戏、视频编辑、3D 渲染或音乐制作等, 这些应用程序需要进行大量计算并且需要很高的性能,这种性能很难通过JavaScript得到保障。

    JavaScript 最初只是一种简单的脚本语言,旨在为充满轻量级超文本文档的网络带来一些交互性。 它被设计为易于学习和编写,但并不是为了速度而设计的。 多年来,浏览器对它们解释 JavaScript 的方式进行了优化,从而带来了重大的性能改进。

    随着JavaScript变得越来越快,开发者可以在浏览器中执行的操作开始逐步扩展。 新的 API 带来了交互式图形、视频流、离线浏览等功能。 反过来,越来越多以前仅限于本机的丰富应用程序开始出现在网络上。 今天,用户可以轻松地从浏览器编辑文档和发送电子邮件,但在某些领域,JavaScript 的性能仍然是无法磨灭的硬伤。

    比如:视频游戏的性能局限一直极具挑战性,因为它们不仅要协调音频和视频,而且通常还要协调物理和人工智能,而WebAssembly 可以解决这些问题。

    3.WebAssembly优势3.1 速度下载执行速度

    WebAssembly 专为速度而生,它的二进制文件比文本 JavaScript 文件小得多,从而下载速度飞快,特别是在低速网络场景。

    WebAssembly的解码和执行速度也更快。 JavaScript 是一种动态类型语言,变量类型不必预先定义,也不需要预先编译。 这使得编写代码变得容易和快速,但这也意味着 JavaScript 引擎有更多的工作要做。 它必须在页面上执行时解析、编译和优化代码。

    解析 JavaScript 涉及将纯文本转换为称为抽象语法树 (AST) 的数据结构,并将其转换为二进制格式。 WebAssembly 以二进制形式交付,解码速度更快。 它是静态类型的,因此与 JavaScript 不同,引擎不需要在编译期间推测将使用什么类型。

    大多数优化发生在源代码编译期间,甚至在它进入浏览器之前。 内存是手动管理的,就像 C 和 C++ 等语言一样,因此也没有垃圾收集。 所有这些都提供了更好、更可靠的性能。据统计, WASM 二进制文件的执行时间仅比相同本机代码的执行时间慢 20%。

    在 JavaScript 引擎中处理 WebAssembly 所花费的相对时间

    WebAssembly缓存机制

    浏览器为WebAssembly提供了很好的优化缓存机制,最大限度的提升浏览器中WebAssembly的运行效率。比如下面使用流式API方法compileStreaming 或 instantiateStreaming 编译或实例化 WebAssembly 模块,优化WebAssembly 代码缓存。

    (async () => {
      const fetchPromise = fetch('fibonacci.wasm');
      const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
      const result = instance.exports.fibonacci(42);
      console.log(result);
    })();

    下面使用的是instantiateStreaming方法:

    (async () => {
      const response = await fetch('fibonacci.wasm');
      const module = await WebAssembly.compileStreaming(response);
      const instance = await WebAssembly.instantiate(module);
      const result = instance.exports.fibonacci(42);
      console.log(result);
    })();

    注意:由于代码缓存取决于资源 URL 以及 .wasm 资源是否最新,开发人员应尽量保持两者稳定。 如果 .wasm 资源是从不同的 URL 获取,它被认为是不同,浏览器必须重新编译模块。 同样,如果 .wasm 资源在缓存中不再有效,则 Chrome 必须丢弃所有缓存的代码。

    3.2 可移植性

    要在设备上运行应用程序,它必须与设备的处理器架构和操作系统兼容。 这意味着需要为想要支持的每种操作系统和 CPU 架构组合编译源代码。 使用 WebAssembly 只需一个编译步骤,应用程序就可以在每一种现代浏览器中运行。

    编译本机代码以在不同平台上运行与编译为 WebAssembly

    开发者不仅可以将自己的应用程序移植到 Web,还可以将现有的大量 C++ 库和开源应用程序移植到 Web。 它是一种几乎所有平台都支持的语言,包括 iOS 和 Android。 WebAssembly可以用作跨 Web 和移动部署的通用语言。

    3.3 灵活性

    到目前为止,JavaScript 一直是 Web 浏览器中唯一完全受支持的语言。 有了 WebAssembly,Web 开发人员将能够选择其他语言,更多的开发人员将能够为 Web 编写代码。 JavaScript 仍然是大多数用例的最佳选择,但当确实需要性能提升时,可以选择使用更加优秀的语言。 UI 和应用程序逻辑等部分用 JavaScript 编写,核心功能在 WebAssembly 中。

    根据MDN数据文档数据,目前完全支持的语言是 C、C++ 和 Rust,但还有许多其他语言正在开发中,包括 Kotlin 和 .NET,这两种语言都已经提供了实验性支持。

    4.使用WebAssembly4.1 准备工作

    如果您已经使用 Emscripten 等工具编译了另一种语言的模块,或者自己加载并运行代码,那么下一步是了解如何使用 WebAssembly JavaScript API 的其他功能。

    Emscripten 是一个开源的编译器,可以将 C/C++ 代码编译为 WebAssembly 编程语言的代码。Emscripten 的底层是基于 LLVM 编译器的,可以查看其开源的 emscripten llvm 和 emscripten clang 。

    让我们通过一步一步的例子来了解如何在 WebAssembly 中使用 Javascript API和如何在网页中加载一个 wasm 模块。

    首先需要一个 wasm 模块,下载 simple.wasm 文件到本机的一个新的目录下。确保本机使用的是支持 webassembly 的浏览器,Firefox 52+ 和 Chrome 57+ 是默认支持 webassembly 的。然后,创建一个简单的 HTML 文件命名为 index.html 和并且你的本机的 wasm 文件处于同一目录下 ( 如果你没有模板可以使用我们提供的 simple template )为了帮助理解发生了什么html加上时间,让我们来看看这个 wasm 模块的文本表示 (也可以在将 WebAssembly 文本格式转换为 wasm见到):

    (module
      (func $i (import "imports" "imported_func") (param i32))
      (func (export "exported_func")
        i32.const 42
            call $i))

    在第二行,你将看到导入有一个两级命名空间 —— 内部函数 $i 是从 imports.imported_func 导入的。编写要导入到 wasm 模块的对象时,需要在 JavaScript 中反映这个两级命名空间。创建一个 节点在你的 HTML 文件中,并且添加下面的代码:

    var importObject = {
      imports: {
          imported_func: function(arg) {
            console.log(arg);
          }
        }
      };

    如上所述,在 imports.imported_func 中有我们导入的函数。

    4.2 加载并使用 wasm 模块

    当导入了对象后,将获取 wasm 文件,使其在 array buffer 可用,然后就可以使用其导出的函数。下面添加以下代码到你的脚本中:

    fetch('simple.wasm')
    .then(res =>
      res.arrayBuffer()
    ).then(bytes =>
      WebAssembly.instantiate(bytes, importObject)
    ).then(results => {
      results.instance.exports.exported_func();
    });

    这样做的结果是执行导出的 WebAssembly 函数 exported_func,又调用了另一个导入的 JavaScript 函数 imported_func, 它将 WebAssembly 实例中提供的值打印到控制台。

    5.本文总结

    本文主要和大家介绍WebAssembly,即浏览器支持的除JavaScript外的新的语言,文章聚焦在什么是WebAssembly、为什么说Web是终态 、为什么需要 WebAssembly 、WebAssembly优势 、如何使用WebAssembly等维度。因为篇幅有限,文章并没有过多展开,如果有兴趣,文末的参考资料提供了优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!

    参考资料

    英文链接:

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论