功能性期货:Bernard Kolobara 的疯子

IT最资讯 2年前 (2022) 导航君
36 0 0

在本月的 Functional Futures 中,我们的嘉宾是 Bernard Kolobara——Lunatic 的创建者,这是一个受 Erlang 启发的 WASM 运行时。

在这一集中,我们讨论了 Lunatic、WebAssembly、BEAM(Erlang 的 VM)等等。
您可以在我们的 YouTube 频道上查看完整剧集,或在我们的播客页面上收听音频版本。

您可以在下面阅读该剧集的一些亮点,为清晰起见进行了编辑。

我们与伯纳德·科洛巴拉谈话的要点

什么是疯子?

Jonn:我不久前了解了 Lunatic,因为我的朋友和最早的 Serokellers 之一 Ante Kegalj 正在为你的东西做出贡献。这是一个小世界。您能否告诉我们的观众 Lunatic 是什么,它是如何演变的,以及这个名字背后的故事是什么?
Bernard:Lunatic 基本上是一个受 Erlang 原则启发的 WebAssembly 运行时。它试图将 Erlang 世界的一些想法带到所有可以编译为 WebAssembly 的编程语言中。
当我说想法时,我的意思是我们正在引入基于进程之间不共享任何状态的并发模型、进程之间的消息发送、链接、构建监督树等抽象,以及 Erlang 的分布式部分.因此,您可以在不同的节点中生成一个进程,然后让它神奇地运行并向它发送消息。
有趣的部分实际上是它的 WebAssembly 组件。
我可以举一个例子来说明 Rust 当前如何与我们的库一起工作,我认为这将描绘出底层基础如何工作的画面。基本上,我首先尝试在 Rust 中做一些类似于 Erlang 的事情,如果你想这样做,你需要使用 async Rust 和 async Rust 中的任务。但是你总是会遇到这样的问题,即 Rust 应用程序只有一个内存空间。所以如果你有一个任务,你生成它,它正在运行,如果它在这个内存空间中弄乱了一些东西,破坏了一些数据,即使你重新启动它,你也不会像在 Erlang 进程中那样获得新的内存。这个容错部分总是缺失且难以获得。
异步 Rust 还有其他问题。例如,如果你在 Rust 中有一些密集的计算部分并且你运行它并且你没有返回给执行程序,它会导致线程卡住并且系统的响应能力将受到影响。还有不同的人体工程学问题,因为你不能在 Rust 中拥有异步特征。所以有很多不同的问题。
在 Erlang 中,感觉非常优雅——您以线性方式编写代码,没有函数着色,没有异步/等待,而且它仍然以高性能运行,我会说,[听不清] 的预定方式。我的想法是“我怎样才能在 Rust 中获得这种体验?”。
同时,我也有点发现了 WebAssembly,WebAssembly 实例与 Erlang 进程非常相似。基本上,每个 WebAssembly 实例都有自己的线性内存,所以它类似于 Erlang 进程的堆内存,并且它们有自己的堆栈。
我们对 Lunatic 所做的就是在字节码中插入抢占点。如果您熟悉 Erlang,我们将使用一个 Rust 应用程序,将其编译为 WebAssembly,插入基本上是一个归约计数器。应用程序运行一段时间后,它将返回。因此,您可以编写任何代码,甚至可以交叉编译,获取一些 C 代码库并将其与您的 Rust 代码库链接在一起,并且一切都始终保持高性能,同时返回,并且工作正常。
基本上,通过 WebAssembly,我们向 Rust 引入了一个新的并发模型。您不仅可以生成异步任务,而且现在可以生成 WebAssembly 实例,这些实例是具有邮箱的小型计算隔离模型(因此您可以向它们发送消息)并且您还可以链接它们。我们尝试将一些 Erlang 的想法引入到 Rust,尤其是现在的 Rust,但我们也专注于将其引入到编译到 WebAssembly 的不同语言中,因为我们的主要抽象是这个 WebAssembly 实例。
关于名称:一开始,我实际上并不了解 WebAssembly,当我想要拥有更高性能的 Erlang 时,我开始研究 Lua 之间的混合,因为我知道 Lua 有一个非常棒的 JIT 编译器。我正在用一些 Erlang 元素做一个 Lua 运行时,我把它命名为 Lunatic。如果你注意到,这个标志是一个月亮。它也受到 Lua 的启发。所以这实际上就是这个名字的由来。我只是保留了这个名字,但这个项目现在完全不同了,它里面并没有真正的 Lua 的任何部分。

为什么选择 WebAssembly?

Jonn:您能否详细介绍一下 WebAssembly 究竟是什么,以及为什么选择 WebAssembly 而不是 JVM,例如?
Bernard:我可以为不熟悉 WebAssembly 的人做一个简短的介绍。基本上,我认为 WebAssembly 名称中的“Web”部分有点不幸。我的意思是,现在 WebAssembly 主要部署在浏览器内部,所以这是有道理的,但 WebAssembly 基本上是一个字节码定义。这是一个非常简单的字节码定义,可以很好地映射到现代 CPU 指令。因此,您可以非常快速地将其编译为计算机可以执行的一些非常有效的代码。
它与 JVM 正好相反:它没有其他任何东西。就像,它没有垃圾收集器,没有任何 API,完全是空白的。这就是为什么它是 C、C++ 和 Rust 等语言的一个很好的目标,因为它不假设任何运行时。它总是意味着嵌入其他地方。
因此,您将一些代码编译为 WebAssembly,然后运行它,例如在浏览器中,然后您可以通过 JavaScript 向 WebAssembly 代码公开一些 API。所以你可以用 JavaScript 说话,但这个接口总是像整数、浮点数一样,没有字符串类型,它总是超低级的。 WebAssembly 为您提供了一种从硬件中抽象出来的方法。
我们也在分布式 Lunatic 的一部分中使用它。您可以生成运行不同操作系统和不同 CPU 架构的不同节点。所以,基本上,你可以说“在另一台机器上运行这个 Rust 函数”,我们在后台传输模块,将其编译到本机架构,然后在那里运行。
一旦你有了这种字节码抽象,你就可以做很多有趣的事情。因为一旦你受限于机器代码,代码首先是不可移植的。但我们也使用 WebAssembly 插入减少计数的指令。基本上,我们所做的是,例如,如果您正在运行一个循环,我们确保您不能在不运行至少一条减少计数指令的情况下进入循环,因此您不能有一个只会占用一个线程的无限循环并且占据所有资源,永不退让。因此,您可以尝试将其插入有趣的点,而不要连续执行太多指令。
这不是对 Rust 或 C 代码的静态分析,我们可以获取任何 WebAssembly 字节码,然后我们对其进行分析,然后我们将指令放在那里,这是即时编译的一个步骤。
我们使用名为 Cranelift 的编译器,它允许您从 WebAssembly 生成高效的机器代码。在这个过程进行的同时,我们只是添加了一些额外的点,比如“现在让给调度程序”,所以整个系统保持响应,我们得到了 Erlang 承诺的低延迟部分。
我认为这真的很有趣,因为 Erlang 给你低延迟,但如果你想要高性能,你需要降级到 C。然后你有 NIF 或者 Rustler 也是一个流行的项目 - 所以你可以链接 Rust native模块,但是一旦你这样做了,你就失去了 Erlang 虚拟机的所有这些响应能力,然后你又要靠自己了。您需要编写一些定期返回的代码,如果您只是选择一个用于图像处理的库,您无法真正修改它以不进行过多的图像处理并返回。
而且我们并不真正关心它,因为您可以像多个项目一样交叉链接并创建一个半 C 半 Rust 的代码库,它会正常工作,我们并不在乎。
WebAssembly 也很重要,正如我所提到的,它基本上没有任何 API。我们称它们为宿主函数——它是在 WebAssembly 模块内部运行的代码和宿主环境之间的层。例如,主机环境的一个很好的例子是浏览器。因此,您可以公开一些函数以在浏览器和 WebAssembly 模型之间进行交互。而我们对 Lunatic 所做的就是公开函数:因此您可以生成新进程、发送消息。因为它对于许多生活在编程语言中的构造也没有意义,例如,在 Rust 中,线程。它甚至意味着什么——在浏览器中生成一个线程——因为大部分执行都是单线程的。因此,当您编译为 WebAssembly 时,此功能不可用。
这有助于我们消除如果有人正在运行一个进程并突然开始产生线程时我们会遇到的所有错误,因为这是设计不允许的。 WebAssembly 为我们提供了许多用于创建良好环境的这些限制,我们设置了您可以做什么和不可以做什么的规则,我们提供了一个并发模型,因此您可以非常接近这个 Erlang应用的感觉。

疯子中的 OTP

Jonn:你提到你不想把架构和类似的东西强加给用户。我想知道您对 OTP 做了什么,您是否使用它?
伯纳德:我认为虚拟机有两个部分。首先,有一个在 Rust 中实现并编译为本机代码然后运行 ​​WebAssembly 的基本运行时。然后是这些来宾库,您可以使用这些库来编写采用底层平台公开功能的应用程序。在平台中,我们提供最少数量的东西,例如流程、链接在一起、消息传递、邮箱以及您在访客空间中无法真正做的事情。但是我们提供的所有其他东西,例如,我们的 Rust 库中有监督者和一些类似于 GenServer 的抽象,因此您可以编写 GenServer 和监督者。
它也类似于 Erlang,因为 OTP 是一个库,所以我们也将它作为一个库提供在它之上。现在我们非常关注 Rust,但这些抽象实际上取决于您使用的语言。在 Rust 中,我们利用类型系统来表达我们的主管如何工作、孩子是什么以及主管策略。这在很大程度上取决于 Rust 类型系统。我们想让编程模型感觉非常接近语言。
例如,在 Rust 中,我们所做的是坚持使用货物和默认工具。因此,如果您了解 Rust,您只需更改一个 cargo 配置,说 runner 是 Lunatic [...] 所以如果您进行 cargo run 它只是在 Lunatic 上运行,直接编译,如果您进行 cargo test,一切都可以开箱即用。因此,开发人员的体验感觉是原生的。
我们还希望这些库对 Rust 开发人员来说是原生的。一开始,我们真的很难将这些 Erlang 的一些非常动态的概念融入到 Rust 类型系统中。有些事情感觉不自然,我们需要多次迭代,直到我们做对了。但我认为,在这一点上,我们有一个非常好的基础,并且我们利用了 Rust 的类型系统。
例如,我们有在最新版本中引入的会话类型。您可以在类型系统中生成通信并预先定义协议的进程,以及会发生什么情况,例如,如果您删除了此之前通信的对象,进程会出现恐慌,然后链接的进程也会出现恐慌。因此,如果某个部件挂起或出现什么问题,类型系统会确保一切都崩溃,而您不只是在某个地方有一个挂起的处理器。我们尝试利用 Erlang 最好的部分,以及 Rust 和类型系统最好的部分,并将它们合并成真正强大的东西。
Jonn:但是我仍然可以编译一个 WASM 程序来向进程发送任意垃圾吗?
伯纳德:是的,你可以,绝对可以。我们还期望人们有时会用不同的语言编写,所以你用 C 编写一个进程,而另一个进程用 Rust 编写,因此彼此之间发送的消息只是缓冲区,因为我们无法做出任何假设。您可以在其中序列化您想要的任何数据并在另一端反序列化,我们将其留给开发人员 - 序列化格式和东西 - 就像:“这是一个缓冲区,在里面写一些东西,我们将将它运送到另一个进程,并让它知道它已经到达”。

去和疯子

Jonn:其他语言呢?例如,Go。有些人喜欢 Golang,我现在理解他们,因为它有泛型,但他们在解释为什么喜欢 Golang 时有时会提到的一件事是它与 WASM 交互。
作为 Golang 用户,使用 Lunatics 功能和主机功能等对我来说有多难?
Bernard:Go 编译器——我不认为它可以编译为 WebAssembly,但是有一个名为 TinyGo 的项目。它基本上是用于嵌入式设备的 Go,它对 WebAssembly 有很好的支持。我从未在实践中尝试过,但我知道这些简单的例子至少有效。所以就像我把一个 hello world 应用程序编译成 WebAssembly,因为它也使用 WASI,WebAssembly 系统接口,Lunatic 理解这些,所以你可以打开文件,写入文件,写入系统,从标准中读取一些东西输入。但是我们没有的是我们没有一个特定的 Lunatic 功能的库:生成新进程、发送消息。这不存在,所以需要有人贡献这个。
目前我们主要关注 Rust,我们希望在扩展之前先确定这个故事,但我认为这样做是完全可行的。正如我所提到的,还有一点很有趣,因为 WebAssembly 不假设任何东西,而且它是一个如此简单的字节码,一旦你将 Go 代码编译为 WebAssembly,你就不能真正在 WebAssembly 内部进行堆栈切换。所以当你在浏览器中编译它们时,goroutines 并不能真正工作。他们使它工作,因为他们用 JavaScript 编写了这部分。当你调用应该等待的东西时,他们会重新安排它,然后在 WebAssembly 模块中的 JavaScript 之间跳转,感觉就像 goroutines 正在同时运行,但这显然在 Lunatic 中不起作用,因为我们没有JavaScript 运行时,我们没有这个主机函数来处理这个问题。
所以你可以在 Go 中使用我们的并发模型——你不会有 goroutines,但你可以使用进程和邮箱。它真的取决于语言,我认为,类型系统和可能性,但也许在 Go 中感觉不错——你会喜欢你编写的 Erlang 方式。而且我认为像从文件中读取这样的简单示例现在开箱即用,它可以编译它,运行没有问题。但是这部分仍然需要有人贡献它们,例如,您需要负责打开网络连接。这还不是 WASI 的一部分,因此需要继续努力。
Jonn:所以对我来说,如果我想,比如说,与 Go 程序交互,我需要编写一个 Rust 包装器,本质上,然后将消息代理到标准输入中。
Bernard:发送消息的 Lunatic 函数非常简单,就像写入这个缓冲区一样。这片内存,然后只是将它发送到这个进程ID。如果您创建了其中一些包装器,您已经可以取消消息发送的功能,因此您实际上不需要代理标准输入。我认为即使您只是手动编码需要做的小部分,也不会做太多工作。
目前,我觉得有这个机会,因为现在人们讨论了很多你应该如何构建你的应用程序。比如,你应该做微服务,你应该做单体。 [听不清。]我觉得这种架构对于 Erlang 来说很自然,因为你已经在用小组件(进程)构建应用程序,如果你现在把它们放在不同的计算机上,它们就不会说话内存,但它们通过消息相互交谈,因此它可以自然扩展,您无需更改编程模型。
使用 Lunatic,您还可以编写不同的语言。现在你不在 Erlang 中,但你有一个用 Rust 编写的 WebAssembly 模块和另一个用 Go 编写的模块,它们也会说话。虽然微服务通常通过 HTTP 进行通信,但它们使用原生消息发送。它的性能更高,因为它跳过的堆栈更少。但目前,正如我所提到的,我们主要关注 Rust,所以我们没有探索整个世界中不同语言的交互以及它应该如何工作的大部分内容。
我知道使用 C 和 Rust 很容易,因为你总是可以在 Rust 中链接 C 代码,这样你就可以处理一些库,如果你需要一个 C 库,它在某些情况下可以工作。但同样重要的是,我认为,你有时会受限于你可以编译到 WebAssembly 的内容,因为一些 C 代码只是嵌入了一些程序集。您不能真正采用任何 x86 程序集并将其编译为 WebAssembly,这没有任何意义。所以有些应用程序甚至无法编译,编译器无法工作,所以总是存在一些限制,但我认为它可以创建一些有趣的架构。

你会向刚接触 WASM 的人推荐 Lunatic 吗?

Jonn:对于像我这样可能了解 Rust 或 Erlang 但对 WASM 一无所知的初学者,你会推荐 Lunatic 作为熟悉 WASM 世界的一种方式吗?如果是这样,你能否强调一些我们 WASM 初学者所在的存储库可以从中汲取灵感吗?
Bernard:我认为,使用 WebAssembly 可以走两条路。首先,我想说的是,人们做的最常见的事情是“我想在浏览器中运行一些 Rust 代码,一些 C 代码”。 Lunatic 在这部分无法为您提供太多帮助,因为我们目前主要关注后端。您可以使用许多很棒的库。与 Rust 一样,bindgen 基本上可以帮助您将 Rust 类型映射到 JavaScript 类型,并使它们之间的通信无缝。因此,如果您专注于浏览器并且想在浏览器中运行一些 Rust,那么这是一个很好的决定。
但是如果你想在后端运行一些 WebAssembly,我认为 Lunatic 是一个不错的选择。我们相当稳定。现在已经发展了两年。我们对其进行了一些重写和迭代,但目前它运行良好。
我写了一个 Telnet 聊天应用程序,我认为这是一个很好的起点,因为它不是那么简单。首先,它还使用了一些 Rust 依赖项,这些依赖项编译为 WebAssembly 并且一切正常。所以,基本上,它是如何工作的,它是 Telnet,它是一个非常简单的协议,你连接到这个服务器,你键入的每个键都会发送到服务器。 UI 在服务器上呈现,然后只是将转义序列的差异发送回您的终端,并且它会发生变化。它有点像 Phoenix LiveView,但如果您熟悉它,则适用于终端。
[…]
我认为它显示了 Lunatic 的所有主要部分,它实际上不是一个大应用程序,我花了一个周末写的,所以你可以很快理解它。我认为这是一个很好的起点,如果你只是想感受一下编写 Lunatic-like Rust 代码的感觉,我认为这是后端 WebAssembly 的一个很好的起点。

我们要感谢伯纳德成为本期节目的嘉宾!他是一个很好的朋友。
如果你想听到他的更多信息,你可以在Twitter上关注他或Lunatic运行时间。此外,你可以在GitHub上查看Lunatic,或者加入他们的discord。
如果你想从我们的播客中听到更多,请务必在你最喜欢的播客平台上订阅我们!

 

文章来源:https://serokell.io/blog/lunatic-with-bernard-kolobara

版权声明:导航君 发表于 2022年6月1日 上午10:40。
转载请注明:功能性期货:Bernard Kolobara 的疯子 | 第八网址导航

相关文章

暂无评论

暂无评论...