许式伟

Go+ SSA 引擎的开发进展

Go+ SSA  ( https://github.com/goplus/gossa ) 是一个基于 SSA  实现的 Go 语言解释器,可以直接从 Go/Go+ 源码运行程序。

在去年的 Go+ 公开课上我分享了 Go+ SSA 脚本引擎简介丨Go+ 公开课 · 第8期,简要介绍了 gossa 主要功能和如何使用,当时在使用上还存在一些限制,无法支持 Go 1.17 amd64 上新的基于寄存器的函数调用方式。

经过了几个月的开发迭代,目前 gossa 重构了部分设计实现,在平台支持和运行效率上有了很大的提高。

  1. 完全支持 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 的优化仍在继续进行中,比如 << 和 >> 运算符执行较慢,还没有做针对优化处理。