Go+ SSA 引擎的开发进展
Go+ SSA ( https://github.com/goplus/gossa ) 是一个基于 SSA 实现的 Go 语言解释器,可以直接从 Go/Go+ 源码运行程序。
在去年的 Go+ 公开课上我分享了 Go+ SSA 脚本引擎简介丨Go+ 公开课 · 第8期,简要介绍了 gossa 主要功能和如何使用,当时在使用上还存在一些限制,无法支持 Go 1.17 amd64 上新的基于寄存器的函数调用方式。
经过了几个月的开发迭代,目前 gossa 重构了部分设计实现,在平台支持和运行效率上有了很大的提高。
完全支持 Go 1.17/Go1.18 新的基于寄存器的函数调用方式。
Go1.17 amd64
Go1.18 amd64 arm64 ppc64/ppc64le
对这些的平台支持与 Go1.17/Go1.18 保持一致,使用 qemu 模拟对应平台测试通过。
函数调用支持实际由 reflectx ( https://github.com/goplus/reflectx ) 库实现。在 reflectx 中实现了对 ABI0 ( 基于堆栈的调用方式)和 ABIInternal (基于寄存器调用方式)的支持,ABI0 的支持我使用了 reflect/unsafe 直接计算实现,而对于 ABIInternal 的支持则是使用 asm 做了一个调用转发。 核心思想就是将对方法的调用转化为对函数(通过 reflect.MakeFunc 动态生成)的调用。
func (t *T) Call(args) => func(t *T, args)2. 脚本运行速度有了较大的提升。
gossa 之前是使用解释器执行的方式,即将生成的 ssa ir 代码按顺序解释运行,运行速度慢。
Go 源码 -> AST -> SSA IR -> 直接解释执行目前重构为将 ssa ir 序列代码预编译为 closure 闭包函数列表,之后执行闭包函数列表的方式实现,类似于 VM 的 bytecode 方式,但 gossa 这里没有 bytecode ,只有闭包列表。
Go 源码 -> AST -> SSA IR -> closure list -> 执行 closure为了测试脚本运行速度,我使用 fib(35) 函数进行测试,我使用了本机上的 python2.7,python 3.9 ,lua 5.4, go-lua,gopher-lua,yaegi,tengo 进行测试,lua 和 python 具有典型代表性,后四个则同样是由 Go 语言实现的脚本引擎,使用官方最新版本。gossa 使用最新版本 v0.3.20 。
fib 算法的 Go 源码表示如下,lua 和 python 版本类似。
func fib(n int) int {if n < 2 {return n}return fib(n-2) + fib(n-1)}
通过 time gossa run fib.go 的方式运行,统计结果如下。(本结果只展示上述 fib 算法本机测试,选择不同的算法或运行环境结果会不同)
程序 | 运行时间 |
lua | 0.91s |
gossa | 2.71s |
python2 | 3.97s |
python3 | 4.86s |
tengo | 3.95s |
go-lua | 4.15s |
gopher-lua | 5.04s |
yaegi | 22.08s |
运行最快为 lua 0.91s,gossa 排在第 2 用时 2.71 s,用时约为 lua 的 3 倍。
下表为 gossa 开发迭代过程中,经历的不同版本运行 fib (35) 时间统计
gossa git tag 版本 | 运行时间 |
gossa v0.3.9 | 1m39s |
gossa v0.3.10 | 31.91s |
gossa v0.3.11 | 31.14s |
gossa v0.3.12 | 27.33s |
gossa v0.3.13 | 13.25s |
gossa v0.3.14~v0.3.16 | 12.20s |
gossa v0.3.17 | 12.05s |
gossa v0.3.18 | 3.90s |
gossa v0.3.19 | 3.52s |
gossav0.3.20 | 2.71s |
从这个表格可以看到迭代优化带来的效果还是相当明显的,最新的 gossa 在运行 fib(35) 上用时大约为 lua 的 3 倍。当然,目前对 gossa 的优化仍在继续进行中,比如 << 和 >> 运算符执行较慢,还没有做针对优化处理。