许式伟

Go+ SSA 引擎优化之基本类型

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

最新的 gossa v0.3.21 对基本类型做了优化,支持了 == != 及单目运算优化。我们先看两个 fib(35) 测试。

  • 测试算法 1 

fib.go

package main
func 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 then         return n     end    return fib(n - 2) + fib(n - 1)end
print(fib(35))
  • 测试算法 2

fibc.go

package main
func 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 then        return 0    elseif n == 1 then        return 1    end    return fib(n - 2) + fib(n - 1)end
print(fib(35))

这次我们的测试只使用我本机上的 lua 5.4.2 和 gossa v0.3.21。

实际上每次的结果都稍有不同,这里都选取多次测试中一个比较好的结果。


fibfibc
lua0.93s1.11s
gossa2.65s3.26s

这里我们可以看到 gossa 目前用时大致为 lua 的 3 倍。

对于上面两种算法的操作,都是 int 类型,这里 int 类型为基本类型,运算较快,可以通过 const 数据检查来加速。还存在有一种重定义基本类型的数据类型,如 type Int int ,基于 int 类型重新定义一个新的 Int 类型。

我们看一个 fib2.go 的例子,这次的源码如下:

package main
type Int int
func 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.Pointer  word 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),  }))}