Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What happens when Golang and Lua meet?

2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly explains "what will happen when Golang and Lua meet". The content in the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "what happens when Golang and Lua meet".

While playing with GitHub, I stumbled upon gopher-lua, a pure Golang Lua virtual machine. We know that Golang is a static language, while Lua is a dynamic language. Golang performs very well in various languages in terms of performance and efficiency, but it certainly cannot compare with Lua in terms of dynamic capabilities. So if we can combine the two, we can combine their respective strengths (manually funny.

In the project Wiki, we can know that the execution efficiency and performance of gopher-lua is only worse than that of bindings implemented by C. Therefore, in terms of performance, this should be a very good virtual machine solution.

Hello World

Here is a simple Hello World program. We first built a new virtual machine, and then DoString (...) on it. Explain how to execute the lua code, and finally shut down the virtual machine. Execute the program, and we will see the string "Hello World" on the command line.

Package mainimport ("github.com/yuin/gopher-lua") func main () {l: = lua.NewState () defer l.Close () if err: = l.DoString (`print ("Hello World") `); err! = nil {panic (err)}} / / Hello World

Compile ahead of time

After viewing the above DoString (...) Method, we find that each time DoString (...) is executed. Or DoFile (...), each executes parse and 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), ")} func (ls * LState) Load (reader io.Reader, name string) (* LFunction, error) {chunk, err: = parse.Parse (reader, name) / /. Proto, err: = Compile (chunk, chunk) /.}

From this point of view, in a scenario where the same Lua code will be executed multiple times (such as in http server, the same Lua code will be executed for each request), if we can compile the code in advance, we should be able to reduce the overhead of parse and compile (if this is hotpath code). According to the Benchmark results, compiling ahead of time does reduce unnecessary overhead.

Package glua_testimport ("bufio"os"strings" lua "github.com/yuin/gopher-lua"github.com/yuin/gopher-lua/parse") / / compile the lua code field 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} / / compile the lua code file 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} 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 代码被执行的场景下,除了可使用提前编译优化性能外,我们还可以引入虚拟机实例池。 因为新建一个 Lua 虚拟机会涉及到大量的内存分配操作,如果采用每次运行都重新创建和销毁的方式的话,将消耗大量的资源。引入虚拟机实例池,能够复用虚拟机,减少不必要的开销。 func BenchmarkRunWithoutPool(b *testing.B) {for i := 0; i < b.N; i++ {l := lua.NewState()_ = l.DoString(`a = 1 + 1`)l.Close()}}func BenchmarkRunWithPool(b *testing.B) {pool := newVMPool(nil, 100)for i := 0; i < b.N; i++ {l := pool.get()_ = l.DoString(`a = 1 + 1`)pool.put(l)}}// goos: darwin// goarch: amd64// pkg: glua// BenchmarkRunWithoutPool-8 10000 129557 ns/op 262599 B/op 826 allocs/op// BenchmarkRunWithPool-8 100000 19320 ns/op 85626 B/op 67 allocs/op// PASS// ok glua 3.467s Benchmark 结果显示,虚拟机实例池的确能够减少很多内存分配操作。 下面给出了 README 提供的实例池实现,但注意到该实现在初始状态时,并未创建足够多的虚拟机实例(初始时,实例数为0),以及存在 slice 的动态扩容问题,这都是值得改进的地方。 type lStatePool struct { m sync.Mutex saved []*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 poolvar luaPool = &lStatePool{ saved: make([]*lua.LState, 0, 4),} 模块调用 gopher-lua 支持 Lua 调用 Go 模块,个人觉得,这是一个非常令人振奋的功能点,因为在 Golang 程序开发中,我们可能设计出许多常用的模块,这种跨语言调用的机制,使得我们能够对代码、工具进行复用。 当然,除此之外,也存在 Go 调用 Lua 模块,但个人感觉后者是没啥必要的,所以在这里并没有涉及后者的内容。 package mainimport ("fmt"lua "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 变量污染 当我们使用实例池减少开销时,会引入另一个棘手的问题:由于同一个虚拟机可能会被多次执行同样的 Lua 代码,进而变动了其中的全局变量。如果代码逻辑依赖于全局变量,那么可能会出现难以预测的运行结果(这有点数据库隔离性中的"不可重复读"的味道)。 全局变量 如果我们需要限制 Lua 代码只能使用局部变量,那么站在这个出发点上,我们需要对全局变量做出限制。那问题来了,该如何实现呢? 我们知道,Lua 是编译成字节码,再被解释执行的。那么,我们可以在编译字节码的阶段中,对全局变量的使用作出限制。在查阅完 Lua 虚拟机指令后,发现涉及到全局变量的指令有两条:GETGLOBAL(Opcode 5)和 SETGLOBAL(Opcode 7)。 到这里,已经有了大致的思路:我们可通过判断字节码是否含有 GETGLOBAL 和 SETGLOBAL 进而限制代码的全局变量的使用。至于字节码的获取,可通过调用 CompileString(...) 和 CompileFile(...) ,得到 Lua 代码的 FunctionProto ,而其中的 Code 属性即为字节码 slice,类型为 []uint32 。 在虚拟机实现代码中,我们可以找到一个根据字节码输出对应 OpCode 的工具函数。 // 获取对应指令的 OpCodefunc opGetOpCode(inst uint32) int {return int(inst >

> 26)}

With this tool function, we can check the global variables.

Package main//... func CheckGlobal (proto * lua.FunctionProto) error {for _, code: = range proto.Code {switch opGetOpCode (code) {case lua.OP_GETGLOBAL:return errors.New ("not allow to access global") case lua.OP_SETGLOBAL:return errors.New ("not allow to set global")}} / / A pair of nested functions checks global variables for _, nestedProto: = range proto.FunctionPrototypes {if err: = CheckGlobal (nestedProto) Err! = nil {return err}} return nil} func TestCheckGetGlobal (t * testing.T) {l: = lua.NewState () proto, _: = CompileString (`print (_ G) `) if err: = CheckGlobal (proto); err = = nil {t.Fail ()} l.Close ()} func TestCheckSetGlobal (t * testing.T) {l: = lua.NewState () proto, _: = CompileString (` _ G = {} `) if err: = CheckGlobal (proto) Err = = nil {t.Fail ()} l.Close ()}

Module

In addition to the possibility that variables may be contaminated, imported Go modules may also be tampered with during operation. Therefore, we need a mechanism to ensure that the modules imported into the virtual machine are not tampered with, that is, the imported objects are read-only.

After consulting the relevant blog, we can modify the _ _ newindex method of Table to set the module to read-only mode.

Package mainimport ("fmt"github.com/yuin/gopher-lua") / / sets the table to read-only func SetReadOnly (l * lua.LState, table * lua.LTable) * lua.LUserData {ud: = l.NewUserData () mt: = l.NewTable () / / sets the field in the table to point to tablel.SetField (mt, "_ index", table) / / restricts the update operation l.SetField (mt, "_ _ newindex") to the table L.NewFunction (func (state * lua.LState) int {state.RaiseError ("not allow to modify table") return 0}) ud.Metatable = mtreturn ud} func load (l * lua.LState) int {mod: = l.SetFuncs (l.NewTable (), exports) l.SetField (mod, "name", lua.LString ("gomodule")) / / set read-only l.Push (SetReadOnly (l, mod) return 1} var exports = map[ string] lua.LGFunction {"goFunc": goFunc } func goFunc (l * lua.LState) int {fmt.Println ("golang") return 0} func main () {l: = lua.NewState () l.PreloadModule ("gomodule", load) / / try to modify the imported module if err: = l.DoString (`local m = require ("gomodule") M.name = "hello world" `); err! = nil {fmt.Println (err)} l.Close ()} /: 1: not allow to modify table Thank you for your reading. The above is the content of "what happens when Golang and Lua meet". After the study of this article, I believe you have a deeper understanding of what will happen when Golang and Lua meet, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report