腾讯云技术分享专栏 JavaScript 与 WebAssembly 进行比较
本文由云 + 社区发表
作者:QQ 音乐前端团队
在识别和描述核心元素的过程中,我们分享了构建 SessionStack 时使用的一些经验法则,这是一个轻量级但健壮且高性能的 JavaScript 应用程序,以帮助用户实时查看和重现其 Web 应用程序的缺陷。
这次我们来分析 WebAssembly 的工作原理,以及在如下几个方面和 JavaScript 进行比较:加载时间,执行速度,垃圾回收,内存使用情况,平台 API 访问,调试,多线程和可移植性。
WebAssembly 的功能
WebAssembly(又名 wasm)是一种高效的,低级别的编程语言。 它让我们能够使用 JavaScript 以外的语言(例如 C,C ++,Rust 或其他)编写程序,然后将其编译成 WebAssembly,进而生成一个加载和执行速度非常快的 Web 应用程序。
加载时间
为了加载 JavaScript,浏览器必须加载所有.js 文本文件。 WebAssembly 在浏览器中加载速度更快,因为只有已编译的 wasm 文件才通过互联网传输。并且 wasm 是一种非常简洁的二进制格式的低级汇编语言,文件更小。
执行
目前 Wasm 比本地代码执行速度慢 20%。这倒是一个令人吃惊的结果,不过,这是一种编译到沙盒环境中的格式并且在很多约束条件下运行,以确保它没有安全漏洞或者很难攻防这个漏洞。与真实的本地代码相比,其实速度下降很小。但是,未来它会更快。
更好的是,它与浏览器无关 - 所有主要引擎都增加了对 WebAssembly 的支持,并且现在提供类似的执行时间。我们来看看简单看看 V8 中发生了什么:
V8 Approach: lazy compilation
在左边,我们有一些 JavaScript 源代码,包含 JavaScript 函数。它首先需要进行分析,以便将所有字符串转换为标记并生成抽象语法树(AST)。AST 是 JavaScript 程序逻辑的内存表示。一旦生成这种表示,V8 直接转到机器码。一般来说,只需要遍历树,生成机器代码,便生成了编译好的函数。从这个过程可以看出,这个阶段并没有编译速度的优势。 现在,我们来看看 V8 管道在下一阶段的功能:
V8 管道设计
这次我们有 TurboFan,V8 的优化编译器之一。当您的 JavaScript 应用程序正在运行时,很多代码在 V8 中运行。TurboFan 可以监控运行缓慢的内容,是否存在瓶颈和热点以优化它们。它将它们推送到后端,这是一个优化的 JIT,它可以优化那些非常耗 cpu 的代码。 虽然它解决了上述问题,但是新的问题在于:分析代码并决定优化哪些内容的过程也会消耗 CPU。这反过来又意味着更高的电池消耗,特别是在移动设备上。 然而,wasm 不一样在于,它会被插入工作流程中,如下所示:
内存模型
WebAssembly 可信和不可信状态 例如,编译成 WebAssembly 的 C ++ 程序的内存是连续的内存块,其中没有 “漏洞”。有助于提高安全性的 wasm 的特性之一是执行堆栈与线性内存分离的概念。在一个 C ++ 程序中,你有一个内存堆,你从堆的底部分配,然后从堆顶增涨堆大小。这便产生一个很多恶意软件利用的漏洞:用一个指针就可以在堆栈内存中查找数据从而更改变量,而这些数据本是你不应该访问到的。
WebAssembly 采用完全不同的模型。执行堆栈与 WebAssembly 程序本身是分开的,因此您无法在其中修改并更改变量等内容。而且,这些函数使用整数偏移而不是指针。函数指向一个间接函数表。然后这些直接计算的数字跳转到模块内部的函数中。它是以这种方式构建的,以便您可以同时加载多个 wasm 模块,形成多个索引列表,并且一切正常。 有关 JavaScript 中内存模型和管理的更多信息,可以查看关于该主题的非常详细的帖子。
垃圾回收
您已经知道 JavaScript 的内存管理是使用垃圾回收器处理的。
WebAssembly 的情况有点不同。它支持手动管理内存的语言。您可以自定义在 WASM 上的垃圾回收模块,但是这个比较复杂。
目前,WebAssembly 是围绕 C ++ 和 RUST 用例设计的。由于 wasm 是非常低级的,因此只有汇编语言上一步的编程语言才易于编译。C 可以使用普通的 malloc,C ++ 可以使用智能指针,Rust 使用完全不同的模式(完全不同的主题)。这些语言不使用 GC,因此它们不需要所有复杂的运行时内容来跟踪内存。WebAssembly 对他们来说是天作之合。
另外,这些语言并不是 100%设计用于调用复杂的 JavaScript 事物,如 DOM。在 C ++ 中编写整个 HTML 应用程序是没有意义的,因为 C ++ 不是为它设计的。在大多数情况下,当工程师编写 C ++ 或 Rust 时,他们的目标是 WebGL 或高度优化的库(例如重数学计算)。
但是,将来 WebAssembly 将支持不附带 GC 的语言。
平台 API 访问
取决于执行 JavaScript 的运行时,可以通过你的 JavaScript 应用程序来访问平台相关的 API。例如,如果您在浏览器中运行 JavaScript,则您有一组 Web APIs,Web 应用程序可以调用它来控制 Web 浏览器/设备功能并访问 DOM, CSSOM, WebGL, IndexedDB, Web Audio API 等。
然而,WebAssembly 模块无法访问任何平台 API。一切都是由 JavaScript 调用的。如果您想访问 WebAssembly 模块中的某些平台特定的 API,则必须通过 JavaScript 调用它。
例如,如果你想 console.log,你必须通过 JavaScript 来调用它,而不是你的 C ++ 代码。这些 JavaScript 调用的成本有所降低。
这并不总是如此。该规范将在未来为平台 API 提供 wasm,并且您将能够在没有 JavaScript 的情况下发布您的应用程序。
Source maps
当您精简 JavaScript 源代码时,您需要一种正确方式调试它。这就需要 Source Maps。基本上, Source Maps 是一种将组合/缩小文件映射回未建立状态的方法。当您为生产而构建时,同时缩小和组合您的 JavaScript 文件,您将生成一个包含原始文件信息的源映射。当您在生成的 JavaScript 中查询某一行和列号时,可以在返回原始位置的源地图中执行查找。
WebAssembly 目前不支持 source maps,因为没有规范,但最终会支持(可能很快)。 当您在 C ++ 代码中设置断点时,您将看到 C ++ 代码而不是 WebAssembly。
多线程
JavaScript 在单个线程上运行。有很多方法可以利用 Event Loop 并利用异步编程。
JavaScript 也使用 Web Workers,但他们有一个非常具体的用例 - 基本上,可能阻塞主 UI 线程的任何 CPU 密集计算都可以进入到 Web Worker 中来提高性能。但是,Web Workers 无法访问 DOM。
WebAssembly 目前不支持多线程。但是,这可能是未来的事情。Wasm 将更接近本地线程(例如 C ++ 样式线程)。拥有 “真实” 的线程将在浏览器中创造出许多新的机会。当然,这将打开更多滥用可能性的大门。
可移植性
如今,JavaScript 几乎可以在任何地方运行,从浏览器到服务器端甚至嵌入式系统。
WebAssembly 被设计为安全和便携。就像 JavaScript 一样。它将运行在支持主机的每个环境中(例如每个浏览器)。就像当年的 Java 的 Applets,WebAssembly 有相同的可移植性的愿景。
哪些场景更合适使用 WA
在 WebAssembly 的第一个版本中,主要关注 CPU 占用大的计算(例如处理数学)。想到的最主流的用途是游戏 - 那里有大量的像素操作。您可以使用您习惯的 OpenGL 在 C ++ / Rust 中编写您的应用程序,并将其编译为 wasm。它会在浏览器中运行。 看看这个(在 Firefox 中运行)
http://s3.amazonaws.com/mozilla-games/tmp/2017-02-21-SunTemple/SunTemple.html。
这是 Unreal engine.。
另一种使用 WebAssembly(性能方面)可能有意义的情况是实现一些库,这是一个 CPU 密集型工作。例如,一些图像处理。
如前所述,由于大多数处理步骤都是在编译期间提前完成的,因此 wasm 可以减少移动设备上的电池消耗(取决于引擎)。
将来,即使您实际上没有编写编译代码,您也可以使用 WASM 二进制文件。您可以在 NPM 中找到开始使用此方法的项目。
对于 DOM 操作和沉重的平台 API 使用,使用 JavaScript 确实很有意义,因为它不会增加额外的开销,并且具有本地提供的 API。
在 SessionStack 中,我们不断增强 JavaScript 的性能,以编写高度优化且高效的代码。我们的解决方案需要提供超快的性能,因为我们不能阻碍客户应用的性能。将 SessionStack 集成到生产 Web 应用程序或网站后,它会开始记录所有内容:所有 DOM 更改,用户交互,JavaScript 异常,堆栈跟踪,失败的网络请求和调试数据。所有这些都在您的生产环境中进行,而不会影响产品的任何 UX 和性能。我们需要大量优化我们的代码并尽可能使其异步。
不仅仅是库文件,当在 SessionStack 中重放用户回话时,我们会渲染用户浏览器中发生的所有事件,并且我们必须重构整个状态,允许您在会话时间线中来回跳转。因为没有更好的选择,为了做到这一点,我们大量使用了 JavaScript 提供的异步机会。
借助 WebAssembly,我们将能够将一些最繁重的处理和渲染转换为更适合作业的语言,并将数据收集和 DOM 操作保留为 JavaScript。
如果你想尝试下 SessionStack,你可以免费开始。有一个免费的计划),每月提供 1000 个会话。
参考:
-
-
此文已由腾讯云 + 社区在各渠道发布
获取更多新鲜技术干货,可以关注我们腾讯云技术社区 - 云加社区官方号及知乎机构号