wordpress中接入支付宝,怎样优化网站app,用墨刀做网站首页,微信app小程序开发目录 go调用lua安装使用注册表调用栈Data modelGo中调用lua APILua调用go打开Lua内置模块的子集使用Go创建模块示例1#xff08;官方#xff09;示例2 关闭一个运行的lua虚拟机虚拟机之间共享lua字节码 go-lua调优预编译虚拟机实例池模块调用 go调用lua
这里比较下两个比较有… 目录 go调用lua安装使用注册表调用栈Data modelGo中调用lua APILua调用go打开Lua内置模块的子集使用Go创建模块示例1官方示例2 关闭一个运行的lua虚拟机虚拟机之间共享lua字节码 go-lua调优预编译虚拟机实例池模块调用 go调用lua
这里比较下两个比较有名的go-lua包
github.com/Shopify/go-lua和github.com/yuin/gopher-lua是两个Go语言库允许Go程序与Lua脚本进行交互。
以下是这两个库之间的主要区别 Shopify/go-lua: Shopify/go-lua是一个用Go编写的Lua解释器。它旨在提供一个轻量级、易于使用的Go和Lua之间的接口。该项目主要关注简单性和易集成性。它提供了调用Go函数和Lua函数之间的绑定。 yuin/gopher-lua: yuin/gopher-lua是一个更丰富功能且活跃维护的Go语言Lua虚拟机实现。它支持广泛的Lua功能并包含一个标准库涵盖许多常见的Lua功能。这个库允许Go代码执行Lua脚本访问Lua变量并注册Go函数供Lua脚本使用。它提供了更全面的文档和更大的社区相比之下优于github.com/Shopify/go-lua。根据我上次更新的信息yuin/gopher-lua已经有了更近期的更新和改进这表明它的维护活跃性更高。
在选择这些库时请考虑项目的具体要求。如果您需要与Lua进行简单轻量级集成github.com/Shopify/go-lua可能已经足够。另一方面如果您需要一个功能更丰富且持续维护的库支持更广泛的Lua功能github.com/yuin/gopher-lua会是一个更好的选择。
安装
GopherLua supports Go1.9.
go get github.com/yuin/gopher-lua使用
GopherLua的API与Lua的运行方式非常相似但是堆栈仅用于传递参数和接收返回值。
Run scripts in the VM.
L : lua.NewState()
defer L.Close()
// 直接执行lua代码
if err : L.DoString(print(hello)); err ! nil {panic(err)
}L : lua.NewState()
defer L.Close()
// 执行lua脚本文件
if err : L.DoFile(hello.lua); err ! nil {panic(err)
}调用栈和注册表大小:
LState的调用栈大小控制脚本中Lua函数的最大调用深度Go函数调用不计入其中。
LState的注册表实现对调用函数包括Lua和Go函数的栈式存储并用于表达式中的临时变量。其存储需求会随着调用栈的使用和代码复杂性的增加而增加。
注册表和调用栈都可以设置为固定大小或自动大小。
当您在进程中实例化大量的LState时值得花时间来调整注册表和调用栈的选项。
注册表
注册表可以在每个LState的基础上配置初始大小、最大大小和步长大小。这将允许注册表根据需要进行扩展。一旦扩展它将不会再缩小。 L : lua.NewState(lua.Options{RegistrySize: 1024 * 20, // this is the initial size of the registryRegistryMaxSize: 1024 * 80, // this is the maximum size that the registry can grow to. If set to 0 (the default) then the registry will not auto growRegistryGrowStep: 32, // this is how much to step up the registry by each time it runs out of space. The default is 32.})
defer L.Close()如果注册表对于给定的脚本来说太小最终可能会导致程序崩溃。而如果注册表太大将会浪费内存如果实例化了许多LState这可能是一个显著的问题。自动增长的注册表在调整大小时会带来一点点性能损耗但不会影响其他方面的性能。
调用栈
调用栈可以以两种不同的模式运行即固定大小或自动大小。固定大小的调用栈具有最高的性能并且具有固定的内存开销。自动大小的调用栈将根据需要分配和释放调用栈页面从而确保任何时候使用的内存量最小。缺点是每次分配新的调用帧页面时都会带来一点小的性能影响。默认情况下一个LState会以每页8个调用帧的方式分配和释放调用栈帧因此不会在每个函数调用时产生额外的分配开销。对于大多数用例自动调整大小的调用栈的性能影响可能是可以忽略的。 L : lua.NewState(lua.Options{CallStackSize: 120, // this is the maximum callstack size of this LStateMinimizeStackMemory: true, // Defaults to false if not specified. If set, the callstack will auto grow and shrink as needed up to a max of CallStackSize. If not set, the callstack will be fixed at CallStackSize.})
defer L.Close()Data model
在GopherLua程序中所有的数据都是LValue。LValue是一个接口类型具有以下方法
type LValue interface {String() stringType() LValueType// to reduce runtime.assertI2T2 costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).assertFloat64() (float64, bool)// to reduce runtime.assertI2T2 costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).assertString() (string, bool)// to reduce runtime.assertI2T2 costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).assertFunction() (*LFunction, bool)
}实现LValue接口的对象是
Type nameGo typeType() valueConstantsLNilType(constants)LTNilLNilLBool(constants)LTBoolLTrue, LFalseLNumberfloat64LTNumber-LStringstringLTString-LFunctionstruct pointerLTFunction-LUserDatastruct pointerLTUserData-LStatestruct pointerLTThread-LTablestruct pointerLTTable-LChannelchan LValueLTChannel-
Go中调用lua
L : lua.NewState()
defer L.Close()
// 加载double.lua
if err : L.DoFile(double.lua); err ! nil {panic(err)
}if err : L.CallByParam(lua.P{// 获取函数名Fn: L.GetGlobal(double),NRet: 1,Protect: true,}, lua.LNumber(10)); err ! nil {panic(err)
}
ret : L.Get(-1) // returned value
L.Pop(1) // remove received valueLua支持多个参数和多个返回值参数好办用lua.LNumber(123)返回值个数也可以是多个调用CallByParam的时候NRet就是返回参数个数Fn是要调用的全局函数名Protect为true时如果没找到函数或者出错不会panic只会返回err。
**GopherLua的函数调用是通过堆栈来进行的调用前将参数压栈完事后将结果放入堆栈中调用方在堆栈顶部拿结果。**调用完成后要以压栈的方式一个一个取回返回值ret : L.Get(-1)。
fib.lua 脚本内容
function fib(n)if n 2 then return n endreturn fib(n-1) fib(n-2)
endpackage mainimport (fmtlua github.com/yuin/gopher-lua
)func main() {// 1、创建 lua 的虚拟机L : lua.NewState()defer L.Close()// 加载fib.lua// Calling DoFile will load a Lua script, compile it to byte code and run the byte code in a LState.// 所以DoFile既加载了文件又执行了字节码if err : L.DoFile(fib.lua); err ! nil {panic(err)}// 调用fib(n)err : L.CallByParam(lua.P{Fn: L.GetGlobal(fib), // 获取fib函数引用NRet: 1, // 指定返回值数量Protect: true, // 如果出现异常是panic还是返回err}, lua.LNumber(10)) // 传递输入参数nif err ! nil {panic(err)}// 获取返回结果ret : L.Get(-1)// 从堆栈中扔掉返回结果// 这里一定要注意不调用此方法后续再调用 L.Get(-1) 获取的还是上一次执行的结果// 这里大家可以自己测试下L.Pop(1)// 打印结果res, ok : ret.(lua.LNumber)if ok {fmt.Println(int(res))} else {fmt.Println(unexpected result)}
}API
Lua调用go
LGFunction类型type LGFunction func(*LState) int
func main() {L : lua.NewState()defer L.Close()L.SetGlobal(double, L.NewFunction(func(state *lua.LState) int {lv : state.ToInt(1) /* get argument */L.Push(lua.LNumber(lv * 2)) /* push result */// 返回值个数return 1}))// 加载编译执行hello.luaL.DoFile(./hello.lua)
}hello.lua中的内容
print(double(100))再来一个案例
package mainimport (fmtlua github.com/yuin/gopher-lua
)func Add(L *lua.LState) int {// 获取参数arg1 : L.ToInt(1)arg2 : L.ToInt(2)ret : arg1 arg2// 返回值L.Push(lua.LNumber(ret))// 返回值的个数return 1
}func main() {L : lua.NewState()defer L.Close()// 注册全局函数L.SetGlobal(add, L.NewFunction(Add))// goerr : L.DoFile(main.lua)if err ! nil {fmt.Print(err.Error())return}
}main.lua内容
print(add(10,20))打开Lua内置模块的子集
打开Lua内置模块的子集可以通过以下方式实现例如可以避免启用具有访问本地文件或系统调用权限的模块。
func main() {L : lua.NewState(lua.Options{SkipOpenLibs: true})defer L.Close()for _, pair : range []struct {n stringf lua.LGFunction}{{lua.LoadLibName, lua.OpenPackage}, // Must be first{lua.BaseLibName, lua.OpenBase},{lua.TabLibName, lua.OpenTable},} {if err : L.CallByParam(lua.P{Fn: L.NewFunction(pair.f),NRet: 0,Protect: true,}, lua.LString(pair.n)); err ! nil {panic(err)}}if err : L.DoFile(main.lua); err ! nil {panic(err)}
}使用Go创建模块
GopherLua除了可以满足基本的lua需要还将Go语言特有的高级设计直接移植到lua环境中使得内嵌的脚本也具备了一些高级的特性
可以使用context.WithTimeout对执行的lua脚本进行超时
可以使用context.WithCancel打断正在执行的lua脚本
多个lua解释器实例之间还可以通过channel共享数据
支持多路复用选择器select
使用Lua作为内嵌脚本的另外一个重要优势在于Lua非常轻量级占用内存极小。
示例1官方
mymodule.go
package mainimport lua github.com/yuin/gopher-luavar exports map[string]lua.LGFunction{myfunc: myfunc,
}func myfunc(L *lua.LState) int {return 0
}func Loader(L *lua.LState) int {// register functions to the tablemod : L.SetFuncs(L.NewTable(), exports)// 注册name属性到moduleL.SetField(mod, name, lua.LString(value))// returns the moduleL.Push(mod)return 1
}mymain.go
package mainimport (./mymodulegithub.com/yuin/gopher-lua
)func main() {L : lua.NewState()defer L.Close()L.PreloadModule(mymodule, mymodule.Loader)if err : L.DoFile(main.lua); err ! nil {panic(err)}
}main.lua
local m require(mymodule)
m.myfunc()
print(m.name)示例2
在Go语言的实现中当我们将一个函数注册为Lua函数通过L.SetFuncs等方法它会被转换为LGFunction类型这样可以确保与Lua C API兼容。返回的整数值用于指示函数的返回值数量通常用0表示成功1表示出错。
实际上这个整数值在Go的GopherLua实现中没有特别的意义因为Go语言不需要遵循C API规范。但为了与标准的Lua C API保持一致Go的GopherLua库仍然要求注册给Lua的函数遵循这个规范即返回一个整数值。
因此在Go的GopherLua中LGFunction类型一定要有返回值以满足与Lua C API的兼容性需求即使这个返回值在Go代码中可能并没有特别的实际意义。
package mainimport (lua github.com/yuin/gopher-lua
)var exports map[string]lua.LGFunction{add: add,
}func add(L *lua.LState) int {// 第一个参数firstArg : L.Get(1).(lua.LNumber)// 第二个参数secondArg : L.Get(2).(lua.LNumber)// 返回值L.Push(firstArg secondArg)// 返回值个数return 1
}func Loader(L *lua.LState) int {// register functions to the tablemod : L.SetFuncs(L.NewTable(), exports)// returns the moduleL.Push(mod)return 1
}关闭一个运行的lua虚拟机
L : lua.NewState()
defer L.Close()
ctx, cancel : context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// set the context to our LState
L.SetContext(ctx)
err : L.DoString(local clock os.clockfunction sleep(n) -- secondslocal t0 clock()while clock() - t0 n do endendsleep(3)
)
// err.Error() contains context deadline exceeded虚拟机之间共享lua字节码
调用DoFile会加载Lua脚本将其编译为字节码并在LState中运行该字节码。
如果您有多个LState都需要运行相同的脚本您可以在它们之间共享字节码这将节省内存。共享字节码是安全的因为它是只读的不会被Lua脚本更改。
// CompileLua reads the passed lua file from disk and compiles it.
func CompileLua(filePath string) (*lua.FunctionProto, error) {file, err : os.Open(filePath)defer file.Close()if err ! nil {return nil, err}reader : bufio.NewReader(file)chunk, err : parse.Parse(reader, filePath)if err ! nil {return nil, err}proto, err : lua.Compile(chunk, filePath)if err ! nil {return nil, err}return proto, nil
}// DoCompiledFile takes a FunctionProto, as returned by CompileLua, and runs it in the LState. It is equivalent
// to calling DoFile on the LState with the original source file.
func DoCompiledFile(L *lua.LState, proto *lua.FunctionProto) error {lfunc : L.NewFunctionFromProto(proto)L.Push(lfunc)return L.PCall(0, lua.MultRet, nil)
}// Example shows how to share the compiled byte code from a lua script between multiple VMs.
func Example() {codeToShare : CompileLua(mylua.lua)a : lua.NewState()b : lua.NewState()c : lua.NewState()DoCompiledFile(a, codeToShare)DoCompiledFile(b, codeToShare)DoCompiledFile(c, codeToShare)
}go-lua调优
预编译
在查看上述 DoString(…) 方法的调用链后发现每执行一次 DoString(…) 或 DoFile(…) 都会各执行一次 parse 和 compile 。
func (ls *LState) DoString(source string) error {if fn, err : ls.LoadString(source); err ! nil {return err} else {ls.Push(fn)return ls.PCall(0, MultRet, nil)}
}func (ls *LState) LoadString(source string) (*LFunction, error) {return ls.Load(strings.NewReader(source), string)
}func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) {chunk, err : parse.Parse(reader, name)// ...proto, err : Compile(chunk, name)// ...
}从这一点考虑在同份 Lua 代码将被执行多次的场景下如果我们能够对代码进行提前编译那么应该能够减少 parse 和 compile 的开销。
package glua_testimport (bufioosstringslua github.com/yuin/gopher-luagithub.com/yuin/gopher-lua/parse
)// 编译 lua 代码字段
func CompileString(source string) (*lua.FunctionProto, error) {reader : strings.NewReader(source)chunk, err : parse.Parse(reader, source)if err ! nil {return nil, err}proto, err : lua.Compile(chunk, source)if err ! nil {return nil, err}return proto, nil
}// 编译 lua 代码文件
func CompileFile(filePath string) (*lua.FunctionProto, error) {file, err : os.Open(filePath)defer file.Close()if err ! nil {return nil, err}reader : bufio.NewReader(file)chunk, err : parse.Parse(reader, filePath)if err ! nil {return nil, err}proto, err : lua.Compile(chunk, filePath)if err ! nil {return nil, err}return proto, nil
}func BenchmarkRunWithoutPreCompiling(b *testing.B) {l : lua.NewState()for i : 0; i b.N; i {_ l.DoString(a 1 1)}l.Close()
}func BenchmarkRunWithPreCompiling(b *testing.B) {l : lua.NewState()proto, _ : CompileString(a 1 1)lfunc : l.NewFunctionFromProto(proto)for i : 0; i b.N; i {l.Push(lfunc)_ l.PCall(0, lua.MultRet, nil)}l.Close()
}// goos: darwin
// goarch: amd64
// pkg: glua
// BenchmarkRunWithoutPreCompiling-8 100000 19392 ns/op 85626 B/op 67 allocs/op
// BenchmarkRunWithPreCompiling-8 1000000 1162 ns/op 2752 B/op 8 allocs/op
// PASS
// ok glua 3.328s虚拟机实例池
新建一个 Lua 虚拟机会涉及到大量的内存分配操作如果采用每次运行都重新创建和销毁的方式的话将消耗大量的资源。引入虚拟机实例池能够复用虚拟机减少不必要的开销。
type lStatePool struct {m sync.Mutexsaved []*lua.LState
}func (pl *lStatePool) Get() *lua.LState {pl.m.Lock()defer pl.m.Unlock()n : len(pl.saved)if n 0 {return pl.New()}x : pl.saved[n-1]pl.saved pl.saved[0 : n-1]return x
}func (pl *lStatePool) New() *lua.LState {L : lua.NewState()// setting the L up here.// load scripts, set global variables, share channels, etc...return L
}func (pl *lStatePool) Put(L *lua.LState) {pl.m.Lock()defer pl.m.Unlock()pl.saved append(pl.saved, L)
}func (pl *lStatePool) Shutdown() {for _, L : range pl.saved {L.Close()}
}// Global LState pool
var luaPool lStatePool{saved: make([]*lua.LState, 0, 4),
}README 提供的实例池实现但注意到该实现在初始状态时并未创建足够多的虚拟机实例初始时实例数为 0以及存在 slice 的动态扩容问题这都是值得改进的地方这是一个可以提交pr的点。
模块调用
gopher-lua 支持 Lua 调用 Go 模块在 Golang 程序开发中我们可能设计出许多常用的模块这种跨语言调用的机制使得我们能够对代码、工具进行复用。
package mainimport (fmtlua github.com/yuin/gopher-lua
)const source
local m require(gomodule)
m.goFunc()
print(m.name)
func main() {L : lua.NewState()defer L.Close()L.PreloadModule(gomodule, load)if err : L.DoString(source); err ! nil {panic(err)}
}func load(L *lua.LState) int {mod : L.SetFuncs(L.NewTable(), exports)L.SetField(mod, name, lua.LString(gomodule))L.Push(mod)return 1
}var exports map[string]lua.LGFunction{goFunc: goFunc,
}func goFunc(L *lua.LState) int {fmt.Println(golang)return 0
}// golang
// gomodule