让 AI vibe 了一个 actor 模型的语言
2026-06-21
当前的 AI 太强大了,新弄一门编程语言的门槛被压得特别低。以前好几个周末的东西,现在几个小时一天内就完成,能覆盖的内容还更多。 弄一个 toy language 也越是没意义。如果纯手写的时候,还能学点东西。现在 AI 写,连学习的作用都没了。 也是因为成本够低,瞎尝试也是无所谓。
由于代码不是亲手写的,所以不聊一行代码。聊聊设计,设计还是"古法"做的,AI 的含量低。
首先是我需要一门什么样的语言?也就是聊需求,聊语言的定位。我期望一门啥都能的语言,那基本上表示基本上就是世界上最垃圾的语言。
如果我要极致的性能,像 C C++ 这些,或者 zig odin rust 这些都是候选。但是垃圾回收就被排除在外了。如果我希望心智负担低,那垃圾回收是挺好的。
如果我希望语言对并发的支持很好,Go Erlang 这些就很不错。
如果我希望语言安全,那么类型系统就很重要,动态脚本语言则不太适合。
如果按语言的能力圈来排,最符合我的口味的,无疑是 Go。编译到 native,性能绝大部分场景足够。支持垃圾回收,类 C 的语法,心智负担低。对于并发编程友好。 但是如果我们把语言不限定成一门呢?如果我们可以底层是 C,上层搭配一个脚本语言,也能取得一个比较好的平衡。像 lua 这类虚拟机语言也是能满足许多应用场景的。
所以这种情况下,也没必要奔着极致的性能走。把任何一个方向,往极端上面走,语言会很有特色,但是难度都会上升很快。玩具嘛,就应该最低成本投入,足够好玩。
所以 tiny actor 大致的定位,就是用字节码虚拟机实现,一个简单的高层语言,不往性能方向做追求。
其实我之前还尝试过另一个版本的事情,是给 cora 语言做 AOT。不是编译成 C,而直接让 AI 帮我直接实现一版编译到汇编,再到 native。 AI 确实是非常强大的,直接编译到 native 而不是编译到 C,能够给 cora 带来一定的性能提升。背后的原因是,我当前是把 C 当成目标指令来用的, 实际上内部为了适配 GC 和 coroutine 等等东西,是有一套内部的 abstract machine。而翻译到的 C 语言只是去把 abstract machine 映射成可实现的东西。 所以会有不少的性能损失,尤其是为了支持 resumable exception,trampoline 的开销很高。
让 AI 帮我直接 abstract machine 到汇编到 native 的代价是什么呢?可维护性明显不行,AI 生成的代码可以跑但是我不知道它咋跑的。尤其是它的 try throw 那一堆对应的实现,我完全无法信任。 另外的代码就是跟 C 交互这块,改成 abstract machine 映射到物理指令和寄存器之后,跟 C 的交互就麻烦太多了,需要调用 C 之前先保存寄存器和部分上下文,调用之后再恢复语言使用的那些寄存器约定。 以前用编译到 C 的方式,约定容易得多,内部的 VM 是可以用 C 结构体定义出来,交互协议也用 C 处理,不涉及具体寄存器。但是修改后就没这个优势了。 所以那个版本,只是 AI 尝试一下,不可能真的实用。也只能证明这个方向,性能有很多提升空间,代价不可接受。
性能定位确定之后,下一个定位就是,高并发。 如果只是方便易用的脚本语言,底层是性能拉满的 C,然后良好的交互,这样的定位已经有一个候选了,就是 lua。那么为什么不直接就是 lua 呢? 相对于 lua,哪些方面可以再提升一些呢?首先一个点是高并发。
lua 一般是游戏里面,嵌入式脚本这样的定位。它是以 C/C++ 这种为主,而以 lua 为辅的。其实我期望换一下位置,以上层语言为主。使用姿势并不是嵌入到 C,而是作为主语言。 C 去实现基础库和模块,提供与操作系统的交互。要说胶水,C 才是主语言与操作系统之间的胶水层。erlang 的虚拟机,beam 更符合这样的定位。 不过 beam 已经太复杂了,都是几十年的软件才积累这么多代码量。而我只需要一个玩具 demo,所以不用搞复杂。
高并发实现模式有好多种。Go 那边是 CSP 的,然后 CML 之前已经在 cora 中尝试过了,这次该试试 actor 啦。 你问 Go 可不可以支持 1M 的 goroutine? 也是可以的。但是代价是几个 G 的内存。而 cora 那边我也支持过 1M 的 coroutine,也是几个 G 的内存。 无论是 Go 还是 cora 都给了初始的的栈太多的内存了,初始就 2K+,如果 1M 就直接到 2G 了,这是纯算 heap in use,如果还考虑 runtime 保留,RSS 就更高。 而且对 GC 是很不友好的。在 Go 里面,GC 本身是支持了多线程,这把 GC 的实现复杂度拉得太高的。
Go 的 GC 需要这么复杂,还有一个因素是它本身是过程式的编程风格,而不是纯函数式。一个 goroutine 还是可以访问到其它 goroutine 的内容,不是纯消息通信这一条路,这就导致 GC 必须是全局的。 纯函数模型下,没那修改,然后 actor 的邮箱归属也很清晰,就会更简化 GC。
在 cora 里面,我是让 VM 的存在来隔离了复杂度,实际的并发能力移到了更上层构架,让 M 个线程驱动 N 个 VM,然后在每个 VM 里面,都可以启动许多的 coroutine 这种形式。 在 cora 里面,VM 才是执行的单元,而 coroutine 属于 VM,GC 也是在 VM 级别,让 VM 内的 coroutine 通过分时执行,达到并发的假像。而 VM 之间是可以运行在不同的线程上,于是是并行的。
tiny actor 里面,我决定再试点不一样的。让 GC 不再是 VM 级别的,而是 actor 级别。这样的好处是,每一个 GC 的卡顿,都只卡顿其实一个 actor,而且不是卡顿 VM 中的全部 coroutine。 粒度划得更细,就可以不增加 GC 实现复杂度(并发,分代) 的前提下,得到更好的实时性。beam 虚拟机就是这样,每个 actor 级别的独立 GC。 具体还说一点细节,tiny actor 的每个 actor,它的堆和栈,是共用的一块内存,分别是从上往下,从下往上增长。当两者相遇的时候,就会触发 GC。而具体的 GC 算法的实现,选用的 semispace copying 的 GC。 这样有一个额外的好处,就是每次触发了 GC,可以分配一个更大的内存块。而不是预先分配比较大的。这比 Go 那样初始就 2K 的 goroutine 栈要更廉价,于是可以支撑更多的 actor 数量。
关于 tiny actor 的并发的 runtime,还有一个取舍得提一下。我在思考的时候,大致有三个选择:
- VM 只支持单线程,单线程内可以许多 actor
- VM 内支持多线程,但是只有单一 scheduler 和全局消息队列
- 支持 M:N 调度,抢占式调度
第一种就是 lua 风格嘛。好处是易于嵌入,单线程 VM 可以方便控制复杂度。第三种,其实就是 beam 虚拟机了,可以说是一个复杂的巨兽。相对于 Go 的 runtime 的差异,beam 的 GC 会好做很多, 它不是一个全局的 GC,而是每个 actor 各种 GC。然后 beam 在调度这一块做得比 Go 会好一点,不是协程那种主动让出,而是每个 actor 都会算 tick 后由 scheduler 切走,软实时性更强。
第一种其实也可以上更上层模块,去补并发能力。无论是 cora 那边的实现,还是云风的 skynet 都是在上层去补的并发框架。tiny actor 我最终选定的是第二种,在 VM 内把多线程支持了,而不是在更上层去补足。 这个选择,用户友好性会更强一些,就是用户不再需要考虑到有线程的存在的,它只知道 actor 的存在,actor 是自动并发了的。为了实现简单,我没有选三,毕竟 skynet 那边也证明了,不搞 beam 那么复杂,其实也够用了。
然后语法的选择。毕竟搞了这么多年的 lisp 对吧,第一版实现还是用的 lisp 语法,编译到虚拟机字节码。完成第一版之后,第二版就开始设计上层语法了,毕竟 lisp 语法是比较反人类的。 如果不用到宏,完成不必要这么搞。我让 AI 大致参考了一下 gleam 语言的语法,这也是一个编译到 beam 虚拟机上的语言。风格更加函数式一些,相对 erlang,也比较现代化。 beam 虚拟机上,除了 erlang,还有一个比较火的是 elixir,elixir 的语法更多 ruby 化一些,然后类型也是后面补上去的。而 gleam 则是从设计开始就更加考虑类型系统。
自举。确定好大致的语法之后,我需要实现自举,tinyactor 缩写就叫 ta 吧。大致的做法是,需要用 ta 实现 parser / compiler 这些,把 ta 可以转成 lisp 的内部 IR。lisp 还是保留了,但是不是对外暴露。 它只作为内部 IR 的定位,从 ta,经过 IR,然后到 tabc,是最终的字节码。然后字节码加上 C 语言实现的一些基础库,就可以完整执行了。
最后代码没啥可看的,都是 AI 生成的。我把 design 可以列出来。 以及这个代码怎么弄出来的可以说一说。
使用的是 pge 这个技能,前面博客其实已经提到过这个技能了。我跟 AI 讨论一下,把 design 确定之后,喂给它。 然后就是让 AI 从 spec 开始,自主推进到任务完成。中间基本上不太需要人类介入干嘛,整个干活的过程记录在这里可以看到。 agent 会自已拆解任务,自己每个任务代理给 subagent 执行,然后会验证,会 review,会提交代码。
只能感叹一下 AI 太强大了。这些原本还有一点点技术含量的活,当下变得一点技术含量都没有了。甚至 AI 把学习的机会和过程,都给抹杀掉了。