Go+ SSA 引擎优化之基本类型
Go+ SSA ( https://github.com/goplus/gossa ) 是一个基于 SSA 实现的 Go 语言解释器,可以直接从 Go/Go+ 源码运行程序。
最新的 gossa v0.3.21 对基本类型做了优化,支持了 == != 及单目运算优化。我们先看两个 fib(35) 测试。
测试算法 1
fib.go
package mainfunc fib(n int) int {if n < 2 {return n}return fib(n-2) + fib(n-1)}func main() {println(fib(35))}
fib.lua
local function fib(n)if n < 2 thenreturn nendreturn fib(n - 2) + fib(n - 1)endprint(fib(35))
测试算法 2
fibc.go
package mainfunc fib(n int) int {if n == 0 {return 0} else if n == 1 {return 1}return fib(n-2) + fib(n-1)}func main() {println(fib(35))}
fibc.lua
local function fib(n)if n == 0 thenreturn 0elseif n == 1 thenreturn 1endreturn fib(n - 2) + fib(n - 1)endprint(fib(35))
这次我们的测试只使用我本机上的 lua 5.4.2 和 gossa v0.3.21。
实际上每次的结果都稍有不同,这里都选取多次测试中一个比较好的结果。
| fib | fibc | |
| lua | 0.93s | 1.11s |
| gossa | 2.65s | 3.26s |
这里我们可以看到 gossa 目前用时大致为 lua 的 3 倍。
对于上面两种算法的操作,都是 int 类型,这里 int 类型为基本类型,运算较快,可以通过 const 数据检查来加速。还存在有一种重定义基本类型的数据类型,如 type Int int ,基于 int 类型重新定义一个新的 Int 类型。
我们看一个 fib2.go 的例子,这次的源码如下:
package maintype Int intfunc fib(n Int) Int {if n < 2 {return n}return fib(n-2) + fib(n-1)}func main() {println(fib(35))}
这个例子与 fib.go 的区别在于使用了新定义的类型 Int 代替 int, gossa 优化后的运行的时间大致为 2.72s ,与 fib.go 稍有差距。
在这里的 int 是基本类型,而 type Int int 是对基本类型定义了一个新的类型,go 本身的 reflect 库里没有对应的 api 支持,我在 reflectx 里实现了一个 NamedTypeOf 函数来实现对应的支持。
在 gossa 中数据存储全部使用 interface{} 类型实现,如果我们知道实际类型为 int 类型,对于 ssa.BinOp 两个数相加实现代码为:
x := fr.reg(ix).(int)y := fr.reg(iy).(int)fr.setReg(ir, x+y)
那么对于类型 type Int int ,如果使用 reflect 如何操作呢?
x := fr.reg(ix)y := fr.reg(iy)vx := reflect.ValueOf(x)vy := reflect.ValueOf(y)n := vx.Int()+vy.Int()r := reflect.New(typ).Elem()r.SetInt(n)fr.setReg(ir, r.Interface())
从这个代码我们可以看到,因为我们无法获取实际类型 Int ,只能通过 reflect 操作,转换为 reflect.Value.Int 相加之后再通过 reflect.New(typ) 重建 Int 类型。这种通过 reflect 实现的方式速度相当慢,用这种方法运行 fib2.go 大约 30s 左右,如何优化呢?
我们可以看一下 interface{} 是如何存储基本类型的。
type eface struct {typ unsafe.Pointerword unsafe.Pointer}
上面是一个 interface{} 的定义,typ 为数据的类型,对应于 reflect.rtype 结构,word 用来存储数据。对 int 类型,这里实际存储的是 int 的指针,我们要想获取对应的 int 数据,可以通过下面类似的代码来实现。
*(*int)(word)对于 type Int int 而言,变换的只是 eface.typ 类型,eface.word 实际存储的仍然是 int 数据的指针,那么我们可以用同样的方式来获取 Int 对应的 int 数据。
func Int(i interface{}) int {return *(*int)((*eface)(unsafe.Pointer(&i)).word)}
我们已经获取到了 int 类型,可以用来进行运算,如何将其转换为 Int 类型呢?这里我们实现一个 Make 函数,对 typ 类型进行替换。
func Make(typ Type, i interface{}) interface{} {p := (*eface)(unsafe.Pointer(&i))p.typ = unsafe.Pointer(typ)return i}
利用这个 Make 函数,我们可以重新实现 Int 类型的加法操作.
x := basic.Int(fr.reg(ix))y := basic.Int(fr.reg(iy))fr.setReg(ir, basic.Make(t, x+y))
可以看到与使用 reflect 实现相比,代码要简化许多,通过这样的优化,gossa 运行 fib2.go 时间降到 2.72 秒,接近 fib.go 的 2.65 秒。
因为要对类似的基础类型 bool / int / uint / float / complex / string 进行操作,所以最后实现了 github.com/goplus/gossa/internal/basic 这个库,除了基本的数据获取和 Make 函数之外,还包括了单目运算 Neg 和 Xor 操作。
最后给出一个 neg float64 和 xor int 的例子。
func NegFloat64(i interface{}) interface{} {p := (*eface)(unsafe.Pointer(&i))v := -*(*float64)(p.word)return *(*interface{})(unsafe.Pointer(&eface{typ: p.typ,word: unsafe.Pointer(&v),}))}func XorInt(i interface{}) interface{} {p := (*eface)(unsafe.Pointer(&i))v := ^*(*int)(p.word)return *(*interface{})(unsafe.Pointer(&eface{typ: p.typ,word: unsafe.Pointer(&v),}))}