DevCycle 团队[1]与 Jon[2] 和 Kris[3] 就 WebAssembly (Wasm) 和 Go 进行了深入讨论!在对 Wasm 的所有内容进行了高层次的讨论之后,我们了解了他们如何以酷炫有趣的方式在生产中使用它。我们以一个热门的非流行部分结束,其中包括“ChatGPT”、“LLM”、“NFT”和“AGI”等流行词
本篇内容是根据2024年7月份#275 Go + Wasm[4]音频录制内容的整理与翻译
过程中为符合中文惯用表达有适当删改, 版权归原作者所有.
Jon Calhoun: 大家好,欢迎来到 Go Time。我今天邀请到了三位嘉宾。首先,Kris,我们的第一个嘉宾是 Jonathan Norris,他是 DevCycle 的联合创始人兼 CTO。他开发了多个面向开发者的产品,并且在设计和构建大规模可扩展系统方面有丰富的经验。Jonathan,你好吗?
Jonathan Norris: 嗨,我很好。很高兴来到这里,也非常期待聊聊我们正在做的工作,还有一些关于 WebAssembly 和 Go 的话题。
Jon Calhoun: 很高兴你能来。我们的下一位嘉宾是 Adam Wootton[5],他是 DevCycle 的首席架构师,负责基础设施、性能和系统可扩展性。Adam,你好吗?
Adam Wootton: 我很好,今天很期待这次对话。
Jon Calhoun: 很高兴你能来。第三位嘉宾是 Brad Van Vugt[6],他之前曾来过我们的节目,讨论过 Battlesnake,这是他创办的一家公司。Battlesnake 今年早些时候被 DevCycle 收购了,现在 Brad 是 DevCycle 的战略与增长主管。Brad,你好吗?
Brad Van Vugt: 很好,很好。你怎么样,Jon?
Jon Calhoun: 我也挺好的。感觉你已经有一阵子没上我们的节目了。
Brad Van Vugt: 是的,确实有一段时间了。
Jon Calhoun: 其实时间并没有很久,但感觉就是这样。
Brad Van Vugt: 很高兴能够回来。
Jon Calhoun: 还有 Kris 也和我们在一起。Kris,你好吗?
Kris Brandow: 我很好。很高兴能回到节目中。我之前有段时间没出现了,所以……
Jon Calhoun: 是的,我觉得我们很久没一起主持节目了。
Kris Brandow: 是啊,大概有一年左右了吧。
Jon Calhoun: 不知道怎么回事,总觉得我会随机出现在一些节目中,总是和同样的两个人一起,我也不知道是怎么发生的……好吧,今天我们要讨论 WebAssembly。首先,我想从介绍 WebAssembly 开始,为什么大家应该关心它,然后我们可以聊聊 Jonathan、Adam 和 Brad 在 DevCycle 使用它的经验,以及你们如何使用它构建不同的东西,从中获得了哪些价值。那么,首先,什么是 WebAssembly?为什么大家应该关心它?
Jonathan Norris: 好的,我可以先说一下。WebAssembly 基本上是一个内存安全的沙盒执行环境,最初是为了将底层的原生代码引入网页而构建的……所以显然,它的品牌名称是 WebAssembly,但实际上,WebAssembly 已经演变成一种跨平台的方式,可以在多个不同的环境中以接近原生的速度执行代码。这些环境包括从网页到边缘计算的无服务器环境,甚至在你的服务器中,以及支持多种不同的语言类型……它们的设计目标是创建可以快速启动的小型二进制文件,并能够在非常接近原生速度的情况下执行,同时还具有非常紧密的安全性和沙盒环境……这大致是 WebAssembly 的定义。
Jon Calhoun: 很棒。那么你们想从哪里开始聊这个话题?你们想从人们可能开始使用 WebAssembly 的地方开始,还是聊聊在 WebAssembly 出现之前,人们通常是怎么做的?
Jonathan Norris: 我们可以谈谈这两方面。我们可以先从 WebAssembly 的常见使用场景开始。我认为 WebAssembly 的一些最常见的应用场景是为网页浏览器带来一些可能原生不存在的功能。你可以想想 Figma……对于那些不知道的人,Figma 是一个设计工具,允许你在基于浏览器的环境中完成整个设计工作流程,而这主要是通过一系列 WebAssembly 库在幕后支持的。如果你想进行深度数据分析,或者运行一些像 Python 或 R 这种工具链中的操作,也有很多这类功能可以通过 WebAssembly 在浏览器中实现。
还有一些游戏……很多人可能在 Hacker News 上看到过,你可以在浏览器中运行《毁灭战士》这样的老游戏。这些游戏基本上就是人们将 C 或 C++ 的代码库编译成 WebAssembly 并加载到浏览器运行时中,让你可以在浏览器中运行这些游戏。但实际上,我认为 WebAssembly 背后的推动力是创造那种可移植的跨平台二进制文件,这些文件可以在浏览器中执行,但现在很多情况下也可以在服务器端甚至边缘计算中运行。边缘计算在 WebAssembly 上真的开始兴起,因为你可以在纳秒级启动一个 WebAssembly 运行时,或者在大多数情况下以毫秒级启动,取决于你的二进制文件有多大,并且可以非常快速地扩展你的边缘计算。所以在边缘使用 WebAssembly 的势头非常强劲。我们也在边缘使用 WebAssembly,同时我们也在多个不同的服务器端语言中使用它来构建 SDK。
Jon Calhoun: 我想,能够在边缘运行并执行这些操作是你提到的原因之一……像 Figma 这样的工具,理论上它们可以用 JavaScript 构建。我不知道性能会如何,但理论上你应该可以用 JavaScript 构建几乎任何软件。那么,这是不是意味着问题不仅仅是 JavaScript 能否完成,而是你可以将它部署在边缘,并做所有额外的事情?抱歉,我感觉我在解释时有点混乱……
我想问的是,是什么主要动机促使人们从尝试用 JavaScript 重写所有库转向 WebAssembly?我觉得你提到的是边缘计算等问题,是否准确?
Adam Wootton: 我认为这还涉及到性能方面的问题。正如你提到的,Figma 可以用 JavaScript 编写,但实际上不可能,因为你永远无法用纯 JavaScript 编写像 Figma 这样复杂的东西,尤其是涉及到很多底层计算的情况。JavaScript 的速度根本不够快。WebAssembly 为你提供了一个更接近硬件的环境来执行代码,所以编写高性能应用程序变得更加容易。而对于像 Figma 这样的应用,你需要处理大量的数据搬移、处理和转换……这些操作都无法在 JavaScript 中高效地编写。你需要某种编译层来处理这些事情。所以我认为这是 WebAssembly 的另一个巨大优势。
Brad Van Vugt: JavaScript 的神奇之处在于它实际上可以非常快,因为 V8 引擎是一个非常神奇的软件。如果你研究过 V8 的内部运行机制以及它如何优化 JavaScript 代码,你会发现它真的是一个神奇的作品,数以百计甚至数百万的开发者投入了无数的时间,才使它成为这么一个惊人的东西。因此,V8 可以将你的 JavaScript 运行得非常快,接近原生速度。但由于它是一个即时编译器(JIT compiler),它需要时间来达到这个速度。所以第一次执行 JavaScript 代码时会比较慢,而随着 JIT 编译器和 V8 以及 SpiderMonkey 等其他引擎开始工作,并将你的 JavaScript 代码编译为越来越底层的代码,你的代码会越来越快。但使用 WebAssembly,你可以直接编译到接近原生速度,运行用 Go、C#、Rust 或 C++ 编写的相同代码,并立即获得类似的性能,而无需等待即时编译器的优化。
Jon Calhoun: 好的。既然这是 Go Time,显然很多听众想知道如何将 Go 与 WebAssembly 结合使用。那么,如果有人想开始使用 WebAssembly 和 Go,该怎么做?
Brad Van Vugt: 有两种方法。你可以将现有的 Go 代码编译成 WebAssembly……有一些相关的项目可以实现这一点。你可以将 Go 代码打包成一个 WebAssembly 二进制文件,如果你想在边缘执行这个 Go 库,或者在网页环境甚至移动环境中执行它---
因为 WebAssembly 可以在移动设备上运行---
你可以很简单地将 Go 代码编译成 WebAssembly 二进制文件。这个过程非常简单,编译目标可以通过搜索引擎找到。
这里给大家的建议是,标准编译会生成比较大的二进制文件,比如多兆字节的文件,虽然听起来不大,但在 WebAssembly 世界中,你想要的是小到个位数 KB 的二进制文件,这样它们可以被快速下载并加载到运行时中。因此,我的建议是使用 TinyGo,它可以大大减少输出的二进制文件大小。当然,它对可以使用的 Go 功能有一些限制,但如果你想这样做,TinyGo 是一个不错的选择。
如果你想在 Go 中执行用其他语言编写的 WebAssembly 代码,Wasmtime 运行时是我们强烈推荐的。它们是 Bytecode Alliance[7](WebAssembly 背后的开源联盟)主要支持的运行时,现在已经经过大量测试并得到良好支持。这些就是我们在所有不同语言的 SDK 中自己使用的运行时。
Jon Calhoun: 那在实际编写代码时,感觉会是怎样的?我在想一个例子。有一些库允许你用 Go 编写代码,基本上生成 React,或者类似的东西。但很多情况下,你最终不得不编写一些非常特定的代码,感觉不像在写 Go 代码,而是 Go 和 React 的混合体。那么在使用 WebAssembly 时,你需要大幅调整对 Go 代码的思考方式吗?还是感觉在写真正的 Go 代码?
Brad Van Vugt: 是的,我觉得这是 WebAssembly 的挑战之一。你需要明确定义你的接口,我认为这是目前使用 WebAssembly 最大的挑战之一。你必须真正理解本机代码和 WebAssembly 代码之间的接口,才能构建出优秀的功能。Adam 可能可以进一步解释这一点,但这是我们遇到的最大挑战。如果你想让 WebAssembly 代码运行得高效---
它在处理 CPU 周期方面表现出色,但在管理内存和数据传输方面不太好。所以你需要非常仔细地考虑传递到 WebAssembly 运行时中的数据量,以及从中获取的数据量,并对这些路径进行优化。
Adam Wootton: 是的,让我补充一下。我们遇到的一个问题是,无法在内存中直接交换复杂的数据结构,无法在 Go 和 WebAssembly 之间共享。因此,你必须考虑如何以一种可以高效序列化和反序列化的方式传递数据。你不能简单地将一个类的实例传递给 WebAssembly 并直接使用它。但你可以考虑如何将这个类转化为一种在内存中可以被 WebAssembly 读取的表示形式。
因此,并没有一种一对一的映射方式,比如“这个对象在 WebAssembly 中变成那个对象”,你需要将其转化为某种内存表示形式。我们最终使用了 protobuf 作为序列化工具,但也有其他方法。我记得 Google 有一个类似 Frame Buffer 的库……我们最初使用的是 JSON,但显然它比 protobuf 慢得多。所以有很多策略可以用来在执行 WebAssembly 模块时高效地传递数据。
Brad Van Vugt: 如果你只是在处理二进制数据,那 WebAssembly 就非常高效了。有很多使用 WebAssembly 进行视频编码或音频过滤的例子……我不会感到惊讶,如果我们今天用的工具 Riverside[8] 也在幕后使用了 WebAssembly 模块来处理音频过滤、视频编码等工作。所以在这些用例中,WebAssembly 可以非常高效地处理结构化的二进制数据,并在 JavaScript 端或 Go 端和 WebAssembly 端之间传递数据缓冲区。
所以有很多图像处理的 WebAssembly 二进制文件可以让你在浏览器中进行图像分析或过滤,并且比在 JavaScript 中做得更快,这些都是通过处理二进制数据来实现的。
Jon Calhoun: 当 Brad 联系我讨论 WebAssembly 时,他提到你们在 DevCycle 有一些非常有趣的 WebAssembly 使用案例。你们能分享一下你们在构建什么项目,使用 WebAssembly 的原因或好处吗?
Brad Van Vugt: 很高兴分享。在 DevCycle,我们正在构建一个功能管理工具,也就是一个功能标识(feature flag)工具,它需要在多个 SDK 和不同的环境中工作。可以想象,对于一个功能标识工具来说,我们希望能够支持尽可能多的客户来使用我们的软件。而要用一个小团队做到这一点,可能需要你为所有不同的语言创建大量的自定义 SDK,或者你可以想办法在多个环境中使用相同的代码库。这就是我们为什么选择 WebAssembly 的原因。我们在之前的产品中已经玩转了这个技术好几年了,后来在 DevCycle 我们决定全力投入,采用一个通用的 WebAssembly 代码库,其中包含了我们所有的核心业务逻辑,比如如何决定哪些用户应该被分配到哪个功能标识,以及如何决定功能的发布和其他重要的业务决策。我们需要这段代码特别强大,经得起各种测试,并且尽可能可靠。
通过使用 WebAssembly,我们能够创建一个非常稳固的库,然后可以在任何需要的地方共享。我们在运行可扩展 API 的工作者(workers)中使用了这个 WebAssembly 库,这些 API 运行在 Cloudflare 的边缘工作者环境中;我们还在所有的服务端 SDK 中使用了这个同样的 WebAssembly 代码,从 Go、Java、Node.js 到 C\#
,我想 Python 和其他几个语言版本也很快就会推出。因此,作为一个功能标识平台,我们可以很自信地支持所有主要的服务端语言,并且可以快速构建这些 SDK,同时确保它们都能按预期工作,因为我们在所有这些 SDK 和 API 中基本上使用了相同的核心代码。这为我们带来了巨大的商业价值,并大大减少了我们需要编写的代码量。
Jonathan Norris: 我很好奇,当时决定采用 Wasm(WebAssembly)的时候---
那是在我加入 DevCycle 之前---
当时 Wasm 的社区是什么样的?Wasm 本身成熟度如何?那时我们还没有看到像 Figma 这样的公司在如此大规模上使用 Wasm,并从中获得巨大好处... 但 WebAssembly 仍然相对较新,还在摸索自己的定位,还在寻找它在整个生态系统中的位置... 那么当时 DevCycle 是基于什么背景决定如此坚决地采用 Wasm 的呢?
Brad Van Vugt: Adam,你想接着讲吗?
Adam Wootton: 当然。我觉得有趣的是,我们一直觉得自己在使用这些技术时有点走在前沿。我会说,当我们第一次开始使用它时,虽然已经有一个相对较大的社区,但我们所使用的很多具体工具还比较新。所以这段时间很有意思,因为我们看着这个技术随着我们的使用而成长。我们使用的一个主要工具是 AssemblyScript[9]。这个语言基本上看起来像 TypeScript,或者说像 JavaScript 的类型版本 TypeScript,但它编译成的是 WebAssembly 模块。当我们开始使用它时,这个语言还很新,可能只发布了不到一年。随着我们的使用,我们看到了它的巨大改进,也看到了社区的快速增长。我们在他们的 Discord 社区中非常活跃,那里有很多人会帮助你解答问题... 所以看到这个生态系统的兴奋和动能不断增长,真的很有趣。
( 译者注: AssemblyScript[10] 是一个把TypeScript 转换到WebAssembly 的编译器. 更多可参考 AssemblyScript 入门指南[11] )
Brad Van Vugt: 我会说,使用 WebAssembly 在浏览器中的生态系统已经相当成熟。我认为浏览器对 WebAssembly 的支持已经存在很长时间了。我不记得具体的年份,但大概是很多年了。实际上,我上周在阿姆斯特丹的 KubeCon 上与 WebAssembly 的一些核心人员交谈,据我了解,WebAssembly 是在大约 12 年前,或者说 10 年前开始的。它的历史比大多数人想象的要长得多,但最初的目标是将更多底层的代码,如 C++、Go 和 Rust 代码引入能够在浏览器环境中执行。但真正让社区开始扩展的是,将这种安全的小型运行时引入到其他地方,比如服务端使用场景、边缘运行时等。这就是社区开始成长的地方。
上周我和一些新兴创业公司聊过,他们都在尝试用 WebAssembly 运行时在很多方面取代 Docker 容器,尤其是在 Kubernetes 和边缘计算环境中。所以这是一个非常令人兴奋的领域,社区也在迅速增长和加速发展。
Kris Brandow: 我发现很有趣的是---
你提到,最初的目标是将一些东西引入浏览器环境。我记得 Unreal Engine[12] (译者注: 即著名的虚幻引擎) 是通过 Emscripten[13] 编译的,运行在浏览器里... 那时我们还没有 WebAssembly,但大家依旧觉得这太棒了。当时的目标是“让它跑在浏览器里”。我想,当时没有人真正想到浏览器其实是一个非常封闭的环境。大多数后端工程师没有特别关注前端,所以他们只是认为“哦,这就是浏览器,它会正常工作的。”但实际上,浏览器在沙盒运行方面非常强大。WebAssembly 也必须做到这一点。
( 译者注: 更多可参考 asm.js 和 Emscripten 入门教程[14] )
所以我一直觉得 WebAssembly 的一个不可思议之处在于,我们一直在尝试在服务端进行沙盒化处理,但那往往变得很混乱,尤其是在虚拟机之外的环境中。所以我觉得这段故事非常引人入胜。
你提到使用 AssemblyScript。我很好奇,用 AssemblyScript 写 WebAssembly 的体验是怎样的?因为我听说写起来会有点困难。它有点像 TypeScript,但有很多地方又不像。而且我知道 WebAssembly 基本上什么都没有。你只得到一个盒子,可以在里面运行一些代码,但别指望它像真正的计算机那样。
Adam Wootton: 是的,我会说根据我们的经验,使用它确实有很多陷阱。我觉得当你刚开始学习它时,它看起来和 TypeScript 非常相似,这可能有点迷惑性。因为实际上,它在底层根本不像 TypeScript。所以当你对性能敏感时,或者对某些细节敏感时,你真的需要深入了解它的实际输出,弄清楚你的代码究竟在 Wasm 环境中做了什么... 因为你很容易写出非常低效的代码,而在 JavaScript 环境中这些低效的代码可能看起来还不错。我想 Jonathan 之前提到过,V8 的 JIT 编译器非常擅长在第一次执行某个低效的代码路径后迅速优化它,以便下次执行时可以快得多。而在 AssemblyScript 世界中,这种情况是不存在的。如果你写的代码在 TypeScript 中看起来不算慢,那么在 AssemblyScript 中它可能会很慢。我们还发现,随着我们深入使用它,越来越多地需要理解底层的内存结构,比如它如何为各种标准对象分配内存... 我们必须弄清楚字符串是如何工作的,类实例化是如何处理的,垃圾回收器什么时候启动... 这涉及很多因素,尤其是在我们试图优化性能时。尽管我们很快能写出能够通过所有单元测试的代码并让它正常工作,但当我们开始对其进行性能分析和基准测试时,我们才意识到,“哦,我们对如何编写代码的许多假设实际上并不成立,我们需要重新考虑一些问题,以提高性能。”
Jonathan Norris: 是的。为了给大家提供一些背景,我们当时有一个 TypeScript 代码库,我们基本上希望它能在 WebAssembly 中工作。所以选择 AssemblyScript 对我们来说是一个自然的选择。我们基本上拿了那个 TypeScript 代码库,然后基本上逐步将所有高级类型更改为低级原始类型。然后我们还必须移除一些不能在 AssemblyScript 中使用的东西,比如闭包。所以我们不得不将代码重构回低级原始类型,感觉好像回到了更简单的时代。我们能够在不到一周的时间内将我们相当庞大的代码库转换为 AssemblyScript,并且让它通过了我们所有的测试并且功能正常... 所以我认为这给我们留下了非常深刻的印象,因为我们能够如此快速地让它运行起来... 但正如我们最近在优化 Go SDK 时遇到的问题一样,尤其是在纳秒级别的优化方面,AssemblyScript 的确给我们带来了一些障碍。
Brad Van Vugt: 是的,我希望我们能进一步深入探讨这个问题。我觉得 DevCycle 的使用场景在这个领域有点独特。而 Jonathan,你已经提到过一些了,我们谈论的是超低的纳秒级延迟,谈论的是边缘计算,谈论的是尽可能接近用户的浏览器环境... 所以我认为 DevCycle 的使用场景远远超出了 WebAssembly 的跨编译和共享二进制文件的好处,实际上我们在谈论的是超微优化和大规模性能问题,特别是从 DevCycle 的基础设施运作来看。所以我希望我们能深入探讨一些技术细节,比如你们是如何从能够运行的 WebAssembly 代码,优化到让它真正跑得非常快的,特别是在 Go 运行时环境下?
Jonathan Norris: 是的,我先介绍一下这个优化问题的背景,Adam 可以深入谈谈一些细节... 我们受到了一个新客户的挑战,他们运行着一个全球 CDN 网络,而这个 CDN 是基于 Go 代码库构建的。我们当时的想法是,“好吧,这是一个真正的挑战。我们必须接受这个挑战,尽可能优化我们的 WebAssembly 代码,以尽量减少对他们 CDN 代码的性能影响,因为他们可能一次要评估几十到几百个功能标识。” 所以这是我们面临的挑战,我们有点天真地接受了这个挑战,决定深入研究 WebAssembly 代码中的每一个小细节,看看我们能做些什么来优化它的性能。是的,沿途我们确实发现了一些有趣的事情。我不知道 Adam 你是否想从我们开始的地方讲起... 因为我们一开始的起点并不好,但最终我们取得了不错的成绩。
Adam Wootton: 是的。正如我之前提到的,我们的初衷只是希望让这个项目的功能与原来的 TypeScript 代码保持一致。因此,我们并没有特别在意性能问题。直到我们开始深入研究性能指标时,才意识到我们需要做多少工作,才能达到目标。我们最初的性能指标---
基本上是通过某个变量对用户进行评估来衡量的。DevCycle SDK 的工作原理是,你询问它“给定这个用户数据,这个变量的值是什么?” 这就是功能标志平台的概念:根据这个用户,他们应该获得这个变量的什么值?在我们最初的代码版本中,每次评估的时间大约是几毫秒,这实在是太慢了。我们讨论的是一个CDN,它的目标是让整个请求处理器的时间在10到15毫秒之间,而这个处理器可能需要评估10到100个 DevCycle 变量,而我们每个变量的评估时间在原始代码中都需要2到3毫秒。显然,这个性能水平是无法接受的。因此,我们最初的大部分工作基本上是减少通过模块边界传递的数据量。前面我们提到,由于无法直接在主机和 Wasm 代码之间共享复杂的对象,因此我们必须传递一组结构化的用户数据和一组从服务器接收的配置数据,然后让 Wasm 返回答案:“这个变量的值应该是多少?”
所以我们最初的主要工作是“我们如何减少传递到边界的数据量,尽量精简?”最后我们发现,我们传递了很多不必要的数据。因此,一旦我们减少了这些数据,性能就大幅提升,从最初的几毫秒缩减到了70微秒,虽然大幅改善,但70微秒仍然太慢了。为了建立一个基准,我们对竞争对手的 SDK 进行了基准测试,发现他们的执行时间在每个操作1000到10000纳秒之间,而我们的时间则是70000纳秒,即70微秒。
所以,我们后来做了很多优化工作,避免了新的内存分配,并更有效地在主机和 Wasm 模块之间共享内存。事实证明,尤其是在 AssemblyScript 中,分配新内存的速度非常慢,因为 AssemblyScript 提供了一个易于使用的垃圾收集器,它确保内存被正确清理。但它使用的是一个相对简单的算法,每次需要分配新内存时都会增加大量时间。
因此,我们做的一个主要优化就是创建一个固定长度的内存缓冲区,作为传递数据的暂存空间,而不是每次都重新分配整个缓冲区。以前的做法是每次都分配一个新的缓冲区,把数据写进去,然后让 Wasm 读取这个缓冲区中的数据。现在我们只需要写入已经分配好的缓冲区的一部分,然后告诉 Wasm 从哪里读取。这样就不需要每次都分配新的内存,速度快了很多。我们的主要工作就是围绕这些优化展开的。我可以继续讲,但你有没有问题想问?
Jonathan Norris: 我对 Go 方面的情况很好奇;这些性能数据非常令人印象深刻。你们是怎么测量的?因为你们是从 Go 运行时的角度测量的,而不是 WebAssembly 项目。你们一开始用的是什么工具来进行这些测量?
Adam Wootton: 是的,我们实际上使用了很多不同的工具,出于不同的原因。我之前提到的这些数字,主要是通过运行 Go 的基准测试获得的,测试的内容是尽可能快地评估变量,使用单线程运行,最后输出每个操作的时间。这就是我们最初获取数据的方式。
随着我们深入研究,我们意识到还有更多因素需要考虑,尤其是当你开始处理多线程时,这对 Go 的 Web 服务器非常重要;你不能让单线程成为所有处理的瓶颈。因此,这改变了我们测量的方式。
除了测量每个操作的时间之外,我们还深入研究了 Wasm 调用的实际时间,以及这些调用中的哪些部分比较慢。这在 Go 端并不容易测量,因为我们使用了 pprof 工具,生成 CPU 配置文件并进行分析。从 Go 的角度来看,WebAssembly 模块边界之后的一切都是一个黑盒。因此,我们的 pprof 输出上显示了一个巨大的箱子,时间在 20 到 30 微秒之间。我们想知道:“这里面发生了什么?我们该如何找出问题所在?”最后我们发现 Node.js 对 WebAssembly 的原生支持非常好,甚至可以输出 WebAssembly 调用的 CPU 性能信息,精确到每次操作的具体执行情况。
因此,我们把在 Go 上测试的同一个模块放到了我们的 Node SDK 中,在那里运行基准测试,并收集 CPU 配置文件。Chrome 浏览器有一个调试工具,可以直接附加到 Node 进程上。通过这种方式,我们得以捕获与 Go 上相同测试的 CPU 配置信息。这让我们能够看到“这是 WebAssembly 代码中的哪个具体调用比较慢”,或者“我们应该把时间花在哪些优化上”。通过这样做,我们又节省了 20 到 30 微秒的时间。我们发现了很多代码中不必要的内存分配、重复调用,或者可以预先转换服务器发送过来的配置数据、以更高效的方式进行迭代处理,这都带来了很大的启发。
这也是一次有趣的调查。然后我们还遇到了一些内存大小方面的问题,比如“它分配了多少内存?有没有内存泄漏?”为了解决这个问题,我们找到了一些工具,它们可以插入 AssemblyScript 编译器,详细输出当前堆上的内容以及分配了哪些内存。通过比较两个堆转储,我们可以发现“这里可能有内存泄漏”或者“这个地方分配了太多的内存”,然后开始深入研究这些问题。所以总体而言,我们使用这些工具对这些问题进行深入挖掘的体验相当不错。
Jon Calhoun: 你提到使用 Node 来研究 WebAssembly,并跟踪哪些部分耗时较长;你觉得未来 WebAssembly 本身会有相关工具让你不必跨语言进行测试吗?没有直接调用 WebAssembly 的其他方式吗?
Jonathan Norris: 我认为 V8 运行时是目前最成熟的 WebAssembly 运行时,因此它具备这些开发工具。而我相信 Code Alliance 团队的 Wasmtime 项目也在开发类似的输出功能,支持他们所支持的各种运行时。但目前的通用建议是,如果你想获得低级别的性能优化,将它插入到支持 WebAssembly 的浏览器引擎中是获得低级别性能信息的最佳方式。Node 运行在 V8 上,因此从服务器端使用来看,它是最简单的方式。
Jon Calhoun: 当你们进行这些优化时,发现其他公司有类似的做法吗?你提到 WebAssembly 尚未被广泛采用,是否有其他公司可以交流经验?
Adam Wootton: 我们还没遇到过和我们做类似事情的公司,即通过 WebAssembly 模块创建可重用的代码块,用于 SDK 和服务器等不同场景。我相信肯定有其他人在做类似的事情,只是我们目前还没遇到。如果有人在做,请联系我们。但我之前提到过 AssemblyScript 社区对我们非常有帮助。我们加入了他们的 Discord 群组,语言的创造者每天都在群里回答问题,给了我们很多指引。没有他们的帮助,我们不可能走到这一步。我们非常感激他们的帮助。不过,确实有很多摸索和尝试的过程,但现在比刚开始时好多了。我们对这些技术的理解比最初更深入了。
Brad Van Vugt: 是的。我也觉得大多数想要开发高性能 WebAssembly 代码库的团队可能会选择使用 C++、Rust 等底层语言作为编译到 WebAssembly 的目标语言。如果你是从头开始一个新项目,而不是像我们这样已有 TypeScript 代码库,我肯定会推荐你从 C++ 或 Rust 这样的语言开始。这两个语言的社区在编译到 WebAssembly 方面比 TypeScript 更为成熟。
这就是我的建议。你也可以选择使用 Go。不过据我了解,从 Go 到 WebAssembly 的转换效率似乎不如 Rust 和 C++。它的效率大概和 AssemblyScript 差不多。所以你不会得到像 C++ 或 Rust 那样优化良好的代码,尽管肯定有很多 Go 开发者在努力优化此事,我相信它会随着时间的推移变得更好。
但如果你想做一些对延迟非常敏感的事情,我还是建议从底层语言开始。我们自己也在朝着将代码迁移到更底层语言的方向发展,以便能进行更直接的优化。因此,未来几个月或几年内,我们可能会逐渐转向这种方向。
Jon Calhoun: 我想确认一下... 我记得你说 WebAssembly 有垃圾收集器,但你们没有使用它,因为它在你们的场景中太慢了,你们需要一个暂存区域。这是否导致了内存泄漏等问题?
Adam Wootton: 这里有几个方面。首先,目前 WebAssembly 本身还没有垃圾收集器,虽然我知道他们正在提议一个标准化的垃圾收集器。目前,垃圾收集器的实现取决于你使用的编译到 WebAssembly 的工具。在我们的例子中,垃圾收集器是由 AssemblyScript 编译器实现的。最终,我们发现当我们去掉所有其他可以优化的部分时,垃圾收集器的实现成了主要的性能瓶颈。每次内存分配操作在性能分析中都显示为相对较慢的部分。
我们也做了一些有趣的工作,调整了垃圾收集器算法中的一些变量,尝试优化其运行频率和中断时间,尽量平滑执行时间的波动。我们还在测量 p50 和 p99 的执行时间,发现它们之间的差异很大,这实际上也是垃圾收集问题。每当垃圾收集器打断执行时,特定调用的执行时间会增加三到四倍,我们需要尽量缩小这个差距。所以我们尝试调整垃圾收集器的参数,看看能否减少这种差异。
至于内存泄漏问题... 我们遇到的一个有趣的情况促使我们使用了之前提到的工具,该工具是 AssemblyScript 的一个插件,它会分析堆中的内容,告诉你哪些内存已经分配了。我们使用这个工具的输出来找出内存泄漏的位置。
有趣的是,当我们使用这个工具时,我们并没有看到新的内存分配,或者堆内存的显著增加。但我们看到的是线性内存的增长:在 WebAssembly 的 Wasmtime 里,当线性内存不足时,它会增加可用的内存,通常是两倍的增长。
但我们发现线性内存在不断增长,而堆内存却没有明显增加。我们最终发现这是我们使用的某个库中的一个 bug。这个库使用了 AssemblyScript 中的一个名为“非托管类”的概念,它跳过了垃圾收集器,但库并没有正确管理这些类的引用。因此垃圾收集器无法追踪它,库本身也忘记了它们的存在,导致大量内存被这些未管理的类占用了,却从未被释放。经过调查,我们与该库的作者合作修复了这个问题,问题就消失了。
Jonathan Norris: 是的,关于 WebAssembly 垃圾收集的背景信息,目前垃圾收集是由 WebAssembly 运行时自行管理的。例如,AssemblyScript 有自己的垃圾收集器,如果你使用 Go 编译为 WebAssembly,它也会自带一个垃圾收集器。不过,有一个非常令人兴奋的提案允许将垃圾收集暴露给主机运行时。例如,未来在 Go 中运行 Wasmtime 时,Go 的原生垃圾收集器可以管理 WebAssembly 中的所有指针和引用。如果是在浏览器或 Node 中运行,V8 的垃圾收集器也可以管理这些引用。这将比当前在 WebAssembly 中嵌入一个垃圾收集器高效得多。我们对此提案非常关注,未来可能会成为这一技术的早期采用者。
Jon Calhoun: 我猜如果有人从 C 或 C++ 转到 WebAssembly,他们仍然需要自己管理内存。这是否也是为什么 C++ 在 WebAssembly 性能方面比较领先的原因之一?
Jonathan Norris: 是的,没错。如果你自己管理内存,那么你就不需要承受垃圾收集的开销,确实会得到更高性能的 WebAssembly 代码库。但你也得自己负责内存管理...
Brad Van Vugt: 我想问一下并发模型的差异。因为选择在服务器端使用 Go 的一个大理由是并发和多线程支持。而在 WebAssembly 上构建的核心代码显然会有一些权衡。你们是如何处理的?你认为未来如何使其更高效?
Adam Wootton: 是的,目前 WebAssembly 的多线程支持还没有完全实现,AssemblyScript 也不支持任何形式的多线程。因此,为了安全地调用我们的 WebAssembly 模块,我们基本上在每次调用时都加了一个互斥锁,以确保不会因为多个 goroutine 同时访问而破坏内存状态。问题是,这显然会为任何试图同时处理数千个请求的 Web 服务器创建一个单一的瓶颈。每次它请求变量值,WebAssembly 模块都会说:“抱歉,现在在处理其他请求。”
我们在 SDK 中的解决方案是创建多个 WebAssembly 模块实例,并在它们之间进行调度。所以当多线程支持最终实现时,我们肯定会集成它。但在此之前,我们采用了“对象池”的方案,每次需要调用时借用一个 WebAssembly 模块,完成工作后再把它归还给池子。这个 SDK 允许你配置对象池的大小,默认情况下是 CPU 核心数。每次 SDK 请求变量值时,都会借用一个 WebAssembly 模块,执行任务后再归还。
这是我们的解决方案,它基本上解锁了更好的并发性能。不过也有一些挑战,例如 WebAssembly 需要保持最新的服务器配置,因此我们必须确保池中每个 WebAssembly 实例都保持最新配置。我们设置了一些机制,从池中取出部分对象进行后台更新,更新完成后再重新投入使用。这需要一些调度,但总体上解决方案效果不错。
Jon Calhoun: 这个管理机制是否需要在每个 SDK 中单独实现?比如在编写 Go SDK 时要设置它,在编写 Rust SDK 时也要设置?
Adam Wootton: 是的,确实是与语言相关的。其实这也是 WebAssembly 的初衷之一:让我们可以轻松编写跨平台的 SDK。但随着我们深入性能优化,我们发现需要为每个平台编写更多特定的代码。例如,之前提到的 Protobuf 序列化。为了在 WebAssembly 边界传递数据,我们从 JSON 转换为 Protobuf。要实现 Protobuf 序列化和反序列化,我们需要在每个 SDK 中实现或使用现成的库。
我们保留了一些旧接口,因此 Wasm 模块仍然可以使用 JSON,也不需要多线程支持,具体取决于平台。在一些平台上,性能已经足够好了,而在 Go 上,我们有更高的性能需求。
Jonathan Norris: 这是关键点... 在 99% 的情况下,Wasm 代码的单线程性能已经非常好了,互斥锁几乎不会影响性能。但对于我们这种特定的高负载 CDN 服务器场景,这种优化确实降低了 p50 和 p99 的执行时间。对于大多数用户,Wasm 代码的单线程性能已经足够好,不会有明显的差异。
Jon Calhoun:好了,现在是时候进入我们不受欢迎的意见环节了。意见不一定要与技术相关,只要是你认为是个不受欢迎的观点就行。我们会把这个观点放到 Twitter 上做个投票,让我们的听众决定他们是否认为它真的不受欢迎。那么,有人愿意先来吗?
Jonathan Norris:好吧,我来吧。我的不受欢迎观点是:到 2030 年,WebAssembly 运行时将取代基于容器的运行时。WebAssembly 的优势在于其严格的安全模型、非常快的启动时间、边缘计算的可扩展性、更小的占用空间以及跨环境的可移植性,这些都将推动从基于容器的运行时(例如 Kubernetes 和边缘工作负载)向 WebAssembly 的转变。WebAssembly 社区内有很多力量在推动这一转变。
Brad Van Vugt:你认为现在实现这一目标的最大障碍是什么?
Jonathan Norris:这是个好问题……我认为可能是语言支持、性能分析和工具支持。正如我们今天讨论的那样,如何让 WebAssembly 更容易优化和分析是一个大问题。还有标准化……WebAssembly 即将迎来很多令人兴奋的变化。我们已经讨论了一些,比如多线程支持和原生垃圾回收支持。
其中一个即将到来的重大变化叫做组件模型(component model),它标准化了多个 WebAssembly 组件之间的通信,使它们能够相互交互,并使你的代码更加模块化,更容易拆分成小块。因此,社区正在为此进行大量工作,推动用这种方式取代容器,特别是在 Kubernetes 和边缘工作负载中。
是的,我认为这些是关键点;如果 WebAssembly 社区能够实现这些即将到来的重大变化,比如组件模型、多线程、垃圾回收支持等,那么我们就将走上这条路,并且在未来几年内我们会看到一些围绕这个领域的新公司崛起。
Brad Van Vugt:我觉得有趣的是,Jonathan,我们经常谈论这个问题,而我认为我的不受欢迎观点恰好相反……也许在时间上可能有差异,但我认为要实现这一点的工作量非常大。你认为 AssemblyScript 是其中的关键环节吗?作为一种核心的原生切入点?
Jonathan Norris:我认为一个更易接近的高级语言作为切入点非常重要。我认为这是 WebAssembly 目前面临的挑战之一:最好的开发环境是底层环境,比如使用 Rust 或 C++。实际上,围绕在 WebAssembly 中运行 JavaScript 或 TypeScript 有不少动力,但这是通过将 SpiderMonkey(Firefox 的 JavaScript 引擎)打包到 WebAssembly 运行时中实现的。他们已经能够在几兆字节的体积内让它运行。所以基本上你可以在 WebAssembly 中运行完整的 SpiderMonkey 运行时,并执行你的 JavaScript 或编译后的 TypeScript 代码……这是许多 Wasm 云或 Wasm 边缘公司的重要切入点之一。但我会说,找到一个在 Wasm 中高效执行的高级语言可能是实现这一目标的最大障碍之一。
Kris Brandow:从另一个角度看,我也在想,你是否看到……嗯,我应该这样说,有很多来自虚拟机(VM)和管理程序(hypervisor)的压力,它们变得非常快,比如 Firecracker[15] 等等……你是否看到这些技术可能会融合,以便你可以获得虚拟机的安全性以及 Wasm 的速度和其他优势?
Jonathan Norris:是的。不要误会,虚拟机经过多年的发展,已经非常优秀了,我们依赖它们来管理许多大规模系统……但我认为容器的体积和 WebAssembly 之间存在数量级的差异。你可以优化容器的大小,让它们变得相对较小,比如十几兆字节……但 WebAssembly 从核心设计上就比这更便携,你讨论的是几十千字节,而不是几十兆字节。而启动时间可以以微秒为单位计算,而不是毫秒、几十毫秒甚至几秒。所以通过使用 WebAssembly,性能上的数量级变化是显而易见的,我认为很多容器化系统很难与之匹敌。
你可以想象一个在边缘大规模运行的平台,比如我们的使用场景。我们有很多 SDK 访问我们的边缘 API。我们的一些客户,比如大型移动应用,可能会发送推送通知,并且会有成千上万甚至上百万的人同时打开他们的应用,当体育比分或重要新闻出现在他们的手机上时,他们会同时打开应用。在这些时刻,我们看到的流量激增是我们平时流量的 100 倍,这些流量会在瞬间涌向我们的边缘端点。而因为我们使用这些边缘平台,它们能够在毫秒内启动数千个 Wasm 和边缘运行时来处理这些流量。使用虚拟机也可以做到这一点,但在这个工具链中有更多的延迟。
所以我认为 WebAssembly 不仅拥有非常严格的安全模型,其启动时间、模块的小体积也确实具有强大的优势。在某些使用场景中,它非常有意义。我不会说它适用于所有场景,显然不会。但对于那些高性能、对延迟敏感的应用场景,比如向全球的移动应用或网页应用发布功能标志,WebAssembly 在我们这个使用场景中显得非常适用。
Jon Calhoun:所以,这意味着在这种情况下……我的看法是,目前使用容器(比如 Docker)的解决方案可能稍微慢一些,但它们适用于 90% 的使用场景;这只是一个随意的数字,但它们确实适用于一大部分场景。而你所说的 WebAssembly 替代方案,因为速度等优势,可能有一大群人其实并不太在意这些。所以我猜想,如果 WebAssembly 要取代 Docker,必须变得和 Docker 一样容易使用。我觉得只有这样才能实现。你觉得现在 WebAssembly 和 Docker 一样容易使用吗?
Jonathan Norris:哦,肯定还没有那么容易。我认为在开发者工具方面还有很多工作要做,才能让它变得容易。我们一直在使用 Cloudflare Workers,对于边缘运行时,它们确实让部署变得超级简单;它们在这方面做得很好。但我认为它的真正好处在于安全性方面。通过 WASI 接口,WebAssembly 模块对其访问权限的控制比虚拟机要严格得多。所以,对于那些非常注重安全的公司,我认为它在某些任务关键型模块中会有很大的价值。
此外,还有很多成本方面的好处。为什么在 Cloudflare Workers、Fastly 或 Netlify 等边缘运行时中运行工作负载要比在 AWS Lambda 这样的环境中便宜得多,原因之一是启动和关闭时间以及它们管理的二进制文件的大小都小得多。这些边缘运行时可以在毫秒甚至更短的时间内启动你的代码,而 Lambda 等基于容器的边缘服务需要更长时间启动,它们的内存占用也更大。因此,这里的成本差异可能非常大。
我们自己通过将工作负载迁移到这些边缘运行时,节省了大量成本。我们不仅构建 SDK,还在边缘运行高规模的 API。拥有小型、便携、快速的运行时,可以在全球各地执行,带来了巨大的成本优势。
Jon Calhoun:这很有道理。好的,Adam、Brad 或 Kris,你们中有谁愿意分享一个不受欢迎的观点吗?好像没有。大家都不敢发表意见。
Kris Brandow:我可以想出一个观点……
Brad Van Vugt:是的,我可以说很多,但我不知道……[笑声] 我不知道是否值得讨论这些。
Kris Brandow:这不就是这个环节的目的吗?说点有争议的东西。
Jon Calhoun:我记得我们之前有个挺有趣的观点,我得去看看是否已经在 Twitter 上发布了。有一次我和 Mat 还有 Matthew Boyle 聊天,他是《Golang 领域驱动设计》一书的作者……他的不受欢迎观点是,你应该可以把笔记本电脑带进电影院,并在看电影时使用。我还是觉得这是我们最不受欢迎的观点之一。
Brad Van Vugt:我有很多带笔记本电脑进电影院的故事,因为我当时要值班,每次进电影院时都会接到电话。我们第一家公司刚开始时,我连续三次进电影院时都接到了电话。
Jon Calhoun:所以你可能同意他的观点,这样你就不用离开电影院了。
Brad Van Vugt:我完全同意他。
Jonathan Norris:我同意我应该被允许这么做,但我不知道其他人是否应该被允许这么做。[笑声]
Adam Wootton:幸运的是,现在我们切换到边缘工作者后,你在看电影时不会再经常接到电话了。
Jonathan Norris:确实如此,确实如此。
Jon Calhoun:好吧,如果没人再想分享什么,我就播放结束音乐,我们结束这一期节目。大家觉得怎么样?
Kris Brandow:我可以再想一个不受欢迎的观点……让我想想。
Jon Calhoun:随你决定,Kris……
Brad Van Vugt:Adam,我觉得你有很多不受欢迎的观点。
Adam Wootton:好吧……我认为 Kubernetes 在技术行业中被过度使用了。我觉得有很多人根本不需要 Kubernetes,却在用它,其实可以用更简单的方式来部署服务器。我们正在使用 Kubernetes,但回头看,我觉得我们不应该使用 Kubernetes。这可能是个不受欢迎的观点……虽然我也看到网上有一些人开始意识到这一点了。
Kris Brandow:我觉得这个观点有点分裂。要么你是被烧过的人,所以你觉得这个观点很受欢迎,要么你还在迷恋 Kubernetes,所以你觉得这个观点很不受欢迎。
Jon Calhoun:就像在 Go 这种语言中使用第三方库或框架。有些人坚决反对,而另一些人则会说:“对我来说用着挺好的,所以我没问题。”
Brad Van Vugt:上周我去了在阿姆斯特丹举行的 KubeCon,我得说,企业界,尤其是那些拥有数百个服务或微服务并运行在 Kubernetes 上的大型企业,对此非常热情。对于这些企业来说,确实很有意义,围绕 Kubernetes 也有很多工具。但如果你是一家小公司,可能很多工作负载都在边缘运行,或者像我们一样主要通过 SDK 运行,并且只需要运行少量服务,那么 Kubernetes 可能有点大材小用了,反而会导致更多的停机时间,而不是提高团队的生产力……我觉得我们目前的经历就是这样。
Adam Wootton:是的,一切都很好,直到你第一次不得不深入 Kube 系统命名空间,去搞清楚哪个内部 pod 出了问题,或者集群出了什么问题。
Jon Calhoun:我完全同意这个观点,可能更好的做法是教会大家还有其他部署方式,并确保大家知道这些方式的存在,而不是让他们觉得 Kubernetes 是“我们最终都会用的东西”。因为我确实见过很多小项目,刚刚起步的时候,他们就已经在用 Kubernetes 了,而实际上他们可能还没有十个用户,你会觉得:“我不确定这是否真的需要。”
Adam Wootton:是的,我觉得很多人并不了解如何用更简单的方式让你的代码在服务器上运行。你并不总是需要构建一个完整的编排系统来让某些东西运行。
Jonathan Norris:是啊,让 Heroku 回来吧……
Adam Wootton:我喜欢 Heroku,Heroku 太棒了。我在很多黑客松上都用过它……
Jon Calhoun:Heroku 确实很棒,直到你需要真正扩展的时候,你的账单就会飙升。
Adam Wootton:哦,是的。但同样,它确实是一个可以快速上手的工具。像我有一些代码,只需要它运行起来。我只需要一个人们可以访问的端点。对于这种需求,它是个很棒的工具,这也是为什么我在黑客松上用它;这是让代码快速部署的最好方式。
Brad Van Vugt:我觉得当 Heroku 流行时,没有人说它很好用,而现在它快退出历史舞台了,大家却对它充满了怀旧之情。我记得那些 Heroku 的美好时光……但当它刚出来时,没人站出来支持它。
Jonathan Norris:谢谢你让我觉得自己老了,Brad……干得好。[笑声]
Jon Calhoun:我对此感受复杂,如果是黑客松项目,我当时确实经常用它。但当它变成一个需要付费的产品,或者我需要稍微扩展一下时,我立刻会想:“我得把我的东西迁移到其他更便宜的地方。”或者在一些创业孵化器的情况下,你会得到一些 Heroku 的资金支持……我不记得具体有多少了,但那是一个非常高的数额,可能有 10 万美元的 Heroku 代金券。所以在这些情况下,你并不在意,因为在一年内很难花掉 10 万美元……所以你根本不在乎。但如果是一个实际的付费业务模式,那就很难让我说:“是的,我真的怀念每月花 40 美元去维护一个服务 10 个人的应用。”
Jonathan Norris:或者每月花 1500 美元去维护一个数据库……
Jon Calhoun:实际上能用的?
Jonathan Norris:……冗余的数据库。
Brad Van Vugt:这就是为什么我喜欢所有这些边缘运行时的按使用量计费模式。只为你实际使用的部分付费,我觉得这才是未来的方向。
Adam Wootton:是啊,我们有一次一个月的流量账单好像只有 10 美元,对吧?
Jonathan Norris:哦,对于某些服务是的。对于某些服务。当然,不是我们的主要平台。
Adam Wootton:不是主要平台,是的。但对于数千万次请求来说,账单大概只有 10 美元,差不多是这样。
Brad Van Vugt:是啊,现在你可以从一些服务中免费获得大量请求。
Jon Calhoun:好吧,Kris,你有想分享的东西吗?
Kris Brandow:是的,我想我有一个稍微……算是稍微有点火药味的观点……但我认为我们现在做的所有 AI 相关的事情,无论是 ChatGPT、Copilot 还是 Midjourney,都是去年的 NFT,或者是之前的 3D 打印机,甚至是很久前的 Segway。这些技术并不会完全消失,但它们会逐渐转向更加细分的市场,而不会实现大家大肆鼓吹的那些功能。3D 打印机是个很好的例子。3D 打印机很棒,确实很出色,很多地方都在用它。但十年前,人们说“每个大学生都会在宿舍里拥有一台 3D 打印机,他们会打印自己需要的一切”,但事实并非如此。
去年我们都在讨论 NFT,大家说“NFT 将彻底改变一切”,但实际上并没有。所以我觉得 AI 现在也处于类似的炒作阶段。我个人会很高兴看到这股热潮过去,然后我们可以进入一个真正使用 AI 的阶段,而不是炒作它的阶段。但我想说的是,现在那些声称我们离通用人工智能越来越近的人,因为我们有了这些大型语言模型,我觉得他们是在异想天开。热潮会消退。我本想说六个月,但感觉有点过于激进了。
Jonathan Norris:你是在暗示风险投资推特圈的人不知道他们在说什么吗?
Kris Brandow:是的。[笑声] 我说过这是个有火药味的观点!
Jonathan Norris:确实有点火药味,我同意你所说的高层次观点,AI 这个词有点误导。我不认为这些大型语言模型已经达到了通用人工智能的水平,但它们非常有用。AI 热潮和去年 NFT 的骗局之间有很大不同。ChatGPT 真的能带来实际的价值。我们内部也在用它,Adam 可以再说一个小时我们如何用 ChatGPT 加速了很多工作。作为开发者,至少对我来说,我从来不是最好的 Bash 编程人员或 SQL 编程人员,但现在我可以在 ChatGPT 中输入:“嘿,帮我写个 SQL 查询,做这个事情。这是表结构,搞定它。” 它就会去做,而且大多数时候都很准确。如果它错了,你只需把错误代码粘贴回 ChatGPT,它会根据错误代码重写查询并修正。这真的让我在过去几个月中大大加快了开发速度。
Kris Brandow:是的……我觉得 NFT 的例子也很好,分布式账本可以用来记录物品的流转过程,追踪物品的所有权链条,这个想法非常有趣,但也非常小众。对于我们这些软件工程师来说,这并不是我们现在特别关心的事情。但在某些领域,比如葡萄酒或艺术收藏,这可能会非常有趣。我觉得 ChatGPT 的一个应用场景是软件工程领域,在这个领域,当它出错时,我们能很清楚地知道它出错了。
所以现在有些事情让我有点烦,这也是我为什么会提出这个不受欢迎的观点。当人们说“哦,大型语言模型产生了幻觉”,我心想:“它没有幻觉,它只是生成了一个统计上正确但实际上错误的结果。” 就像自动纠错功能出错一样,它只是出错了,它并没有做任何不同的事情。但人们只是在以不同的方式解读它。所以我觉得在我们知道正确答案的领域,比如说我们知道正确答案应该是什么样子,AI 是可以用的。但在我们不知道的时候,它可能会非常危险。
Jonathan Norris:我觉得这和自动驾驶有些类似。所有关于自动驾驶汽车的炒作……我们还没有接近那种完全自动驾驶的汽车。我住在多伦多……在冬天开车的时候,路上有积雪,我们还远远没有达到自动驾驶的水平。但我们已经到了每辆新车---
甚至是便宜的丰田车---
都配备了非常好的车道保持功能,这在高速公路上表现非常出色,只要天气好。这就是我对 AI 的看法。也许我们在未来 20 年到 50 年内都无法实现完全自动驾驶的汽车,但我们现在确实看到了逐步的改进,我觉得 AI 也是类似的。
Kris Brandow:嗯,你已经同意我的观点了。[笑声]
Jonathan Norris:我确实有点同意了。
Jon Calhoun:我觉得问题在于如何看待它……如果你期待 AI 能够永远 100% 准确并完美执行所有任务,我觉得任何这样想的人都会失望,因为要做到这一点真的非常难。自动驾驶汽车也是一样。但我认为有些场景不需要 100% 的准确性,只要它在某些特定场景中表现得非常出色就足够了。自动驾驶汽车就是一个例子,如果我有一辆车,它只会在高速公路上自动驾驶,不需要在城市中处理复杂的情况,这已经比我现在的状况要好得多了。
Jonathan Norris:我们已经非常接近这个目标了。
Jon Calhoun:是啊,所以它不需要完美。它可以跳过所有其他的边缘情况,只要能在几个核心场景中表现得非常好就行。我觉得我们可以朝这些方向努力,但问题在于人们脑海中总有一个“我们正在朝完美自动驾驶技术前进”的想法。对于 AI 来说,人们可能会期待 AI 能够 100% 准确,从不犯错……我觉得那些期待这种结果的人会失望。
但我想说,ChatGPT 和其他类似工具让我感到满意的一点是,它让很多非技术人员看到了他们可以用计算机解决生活中琐碎问题的潜力。对于开发者来说,我们早就知道很多事情可以被轻松解决……就像我看着别人用 ChatGPT 做的事,我会觉得“你其实可以不用 ChatGPT 也能轻松做到这些。” 但他们只是之前不知道怎么做,现在他们可以利用这个工具,这很好。我甚至在想,如果现在发布了 Clippy 这样的东西,它可能永远不会消失。而当年发布时……
Adam Wootton:微软现在其实就是在重新整合类似 Clippy 的东西到 Office 套件中,或者他们正在添加一个聊天界面。
Jon Calhoun:我倒希望是 Clippy 这个形象重新出现。
Adam Wootton:我相信有人会做一个插件,把图标换成 Clippy……但我觉得这是个“最后 10%”的问题。自动驾驶和 AI 的区别在于,对于自动驾驶,你之前提到的完美的车道保持功能,在高速公路上非常实用,今天就已经很有用了。但问题是,如果你想让它在城市街道上完全自动驾驶,你仍然需要注意道路情况。所以车道保持之外的任何功能在没有完全实现之前并没有那么实用……因为即使它在城市中大部分时间能自动驾驶,这对司机也没有帮助,因为你还是得把手放在方向盘上,还是得保持注意力。所以它并不真正减少司机的负担,更多的是一个玩具。
但 AI 即使只完成了 90%,你已经可以用它做很多实用的事情了。我觉得现在更多的是用户体验的问题。我喜欢刷 Twitter,看看人们如何用这些工具想出新颖有趣的点子。很多人可能根本不会想到去那样做。但这并不是问题,因为再过五年,我们的大多数软件可能都会在后台使用这些技术,提供现在人们用 ChatGPT 实现的那些功能;届时界面会更好,你不会需要解释它该做什么,它会自动明白该怎么做。所以我觉得这一点非常令人兴奋,看到它将如何无缝地融入现有软件中。
Kris Brandow:是的。我一直觉得这些大型语言模型有点像高级编译器,如果你不知道它们在做什么,它们看起来就像魔法……但实际上,编译器在某种程度上确实也是魔法。你输入了东西,然后得到了结果,你会觉得:“我不知道这是怎么发生的。” 但当你了解它所用的基础组件时,你会发现:“哦,原来如此,我能理解,这并不是超出理解的事情。” 但当你把规模放大时,它确实会变得很复杂,因为人类不擅长处理大数字。就像经典的例子:“十亿美元和一百万美元的区别就是十亿美元。” 当你这样说时,人们会觉得:“这听起来不对。” 但如果你说:“一千美元和一美元的区别是一千美元。” 人们会说:“哦,当然了。”
所以当你面对大量东西时,无论是编译器中的大量编译指令,还是大型语言模型中的大量权重,它开始变得有趣,你可以应用它。但我觉得这仍然是在小众领域中的应用,比如:“哦,这些是我们可以用这项技术做的很酷很有趣的事情。”
也许更有火药味的观点是,无论是自动驾驶还是通用人工智能,我们永远也无法真正实现这些目标,但继续追求它们仍然是好的,因为所有的副产品对人类生活非常有用,并能增强人类的生活。
Adam Wootton:我听到很多人说,如果我们不能实现通用人工智能,那我们也无法实现自动驾驶。[笑声]
Kris Brandow:我觉得有很多理由可能让我们永远无法实现自动驾驶……我不知道,也许未来我们根本不需要汽车。也许我们会把公共交通做到极致,再也不需要开车了。
Jonathan Norris:哦,千万别让我谈公共交通……我可以再讲一个小时。[笑声]
Adam Wootton:多伦多刚开始建造市中心的第三条地铁线,这可是 50 年后的事情了。[笑]
Jon Calhoun:我住在美国的乡村,所以公共交通在这里根本不存在。我住的这个小镇有两辆出租车,而且不一定总在运营。你需要打电话预约,他们不会随叫随到。Lyft 和 Uber 在我这里都不存在。
Kris Brandow:你知道的,总有些希望的曙光……我看过一个很酷的视频,讲的是瑞士的铁路系统。视频里有一个随机的滑雪缆车和咖啡馆,我记得是关于一些徒步路线的,视频里说:“哦,我们每 30 分钟就有一趟火车,全天候运营。” 我想:“哦,原来农村火车也可以这样运行?好吧……” 但对于美国人来说,尤其是生活在农村或郊区的人来说,想象高质量的公共交通确实很难。
Jon Calhoun:我觉得有时候……他们需要亲身体验这些好处才能真正感受到。有一次……我忘记是哪个城市了……好像是田纳西州和另一个城市,他们在讨论建设一条火车线路,可以让你在几个小时内从一个城市到另一个城市……在一个播客上,我听到有人在谈论这个,他们说:“搞这个有什么意义?” 我想:“是啊,确实没什么用,直到你突然发现,你可以轻松地去另一个城市,然后你会发现自己比以前更多地做这些事,因为它很方便。” 而如果你得开车八个小时,或者你得飞过去,面对所有与飞行相关的麻烦事,你可能不会经常做这些事。但坐火车大多数时候是非常容易的。
Adam Wootton:是的,我记得当我第一次去伦敦时,发现我可以坐火车在两小时内到达巴黎时,我简直不敢相信。我想:“等等,我本来计划是去伦敦旅游,但我也可以顺便去巴黎,而且只需要坐火车就能到。” 这太疯狂了。
Jon Calhoun:好吧,我觉得差不多了。我来放结束音乐,然后停止录音。谢谢大家的参与。Jonathan、Adam、Brad,感谢你们的加入。Kris,谢谢你帮助我主持节目。
Kris Brandow:当然,没问题。
Jonathan Norris:谢谢你邀请我们。
Brad Van Vugt:谢谢你邀请我们。
DevCycle 团队: https://github.com/DevCycleHQ
[2]Jon: https://github.com/jonathannorris
[3]Kris: https://github.com/skriptble
[4]#275 Go + Wasm: https://changelog.com/gotime/275
[5]Adam Wootton: https://github.com/ajwootto
[6]Brad Van Vugt: https://github.com/bvanvugt
[7]Bytecode Alliance: https://bytecodealliance.org/
[8]Riverside: https://riverside.fm/
[9]AssemblyScript: https://www.assemblyscript.org/
[10]AssemblyScript: https://github.com/AssemblyScript/assemblyscript
[11]AssemblyScript 入门指南: https://juejin.cn/post/6844904002786689031
[12]Unreal Engine: https://www.unrealengine.com/
[13]Emscripten: https://github.com/emscripten-core/emscripten
[14]asm.js 和 Emscripten 入门教程: https://www.ruanyifeng.com/blog/2017/09/asmjs_emscripten.html
[15]Firecracker: https://github.com/firecracker-microvm/firecracker