日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網營銷解決方案
當Go遇上了Lua,會發(fā)生什么

在 GitHub 玩耍時,偶然發(fā)現(xiàn)了 gopher-lua ,這是一個純 Golang 實現(xiàn)的 Lua 虛擬機。我們知道 Golang 是靜態(tài)語言,而 Lua 是動態(tài)語言,Golang 的性能和效率各語言中表現(xiàn)得非常不錯,但在動態(tài)能力上,肯定是無法與 Lua 相比。那么如果我們能夠將二者結合起來,就能綜合二者各自的長處了(手動滑稽。

網站建設哪家好,找創(chuàng)新互聯(lián)!專注于網頁設計、網站建設、微信開發(fā)、微信小程序、集團企業(yè)網站建設等服務項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了新巴爾虎左免費建站歡迎大家使用!

在項目 Wiki 中,我們可以知道 gopher-lua 的執(zhí)行效率和性能僅比 C 實現(xiàn)的 bindings 差。因此從性能方面考慮,這應該是一款非常不錯的虛擬機方案。

Hello World

這里給出了一個簡單的 Hello World 程序。我們先是新建了一個虛擬機,隨后對其進行了 DoString(...) 解釋執(zhí)行 lua 代碼的操作,***將虛擬機關閉。執(zhí)行程序,我們將在命令行看到 "Hello World" 的字符串。 

 
 
 
 
  1. package main  
  2. import (  
  3.     "github.com/yuin/gopher-lua"  
  4. )  
  5. func main() {  
  6.     l := lua.NewState()  
  7.     defer l.Close()  
  8.     if err := l.DoString(`print("Hello World")`); err != nil {  
  9.         panic(err)  
  10.     }  
  11. }  
  12. // Hello World 

提前編譯

在查看上述 DoString(...) 方法的調用鏈后,我們發(fā)現(xiàn)每執(zhí)行一次 DoString(...) 或 DoFile(...) ,都會各執(zhí)行一次 parse 和 compile 。 

 
 
 
 
  1. func (ls *LState) DoString(source string) error {  
  2.     if fn, err := ls.LoadString(source); err != nil {  
  3.         return err  
  4.     } else {  
  5.         ls.Push(fn)  
  6.         return ls.PCall(0, MultRet, nil)  
  7.     }  
  8. }  
  9. func (ls *LState) LoadString(source string) (*LFunction, error) {  
  10.     return ls.Load(strings.NewReader(source), "")  
  11. }  
  12. func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) {  
  13.     chunk, err := parse.Parse(reader, name)  
  14.     // ...  
  15.     proto, err := Compile(chunk, name)  
  16.     // ...  

從這一點考慮,在同份 Lua 代碼將被執(zhí)行多次(如在 http server 中,每次請求將執(zhí)行相同 Lua 代碼)的場景下,如果我們能夠對代碼進行提前編譯,那么應該能夠減少 parse 和 compile 的開銷(如果這屬于 hotpath 代碼)。根據 Benchmark 結果,提前編譯確實能夠減少不必要的開銷。 

 
 
 
 
  1. package glua_test  
  2. import (  
  3.     "bufio"  
  4.     "os"  
  5.     "strings"  
  6.     lua "github.com/yuin/gopher-lua"  
  7.     "github.com/yuin/gopher-lua/parse"  
  8. )  
  9. // 編譯 lua 代碼字段  
  10. func CompileString(source string) (*lua.FunctionProto, error) {  
  11.     reader := strings.NewReader(source)  
  12.     chunk, err := parse.Parse(reader, source)  
  13.     if err != nil {  
  14.         return nil, err  
  15.     }  
  16.     proto, err := lua.Compile(chunk, source)  
  17.     if err != nil {  
  18.         return nil, err  
  19.     }  
  20.     return proto, nil  
  21. }  
  22. // 編譯 lua 代碼文件  
  23. func CompileFile(filePath string) (*lua.FunctionProto, error) {  
  24.     file, err := os.Open(filePath)  
  25.     defer file.Close()  
  26.     if err != nil {  
  27.         return nil, err  
  28.     }  
  29.     reader := bufio.NewReader(file)  
  30.     chunk, err := parse.Parse(reader, filePath)  
  31.     if err != nil {  
  32.         return nil, err  
  33.     }  
  34.     proto, err := lua.Compile(chunk, filePath)  
  35.     if err != nil {  
  36.         return nil, err  
  37.     }  
  38.     return proto, nil  
  39. }  
  40. func BenchmarkRunWithoutPreCompiling(b *testing.B) {  
  41.     l := lua.NewState()  
  42.     for i := 0; i < b.N; i++ {  
  43.         _ = l.DoString(`a = 1 + 1`)  
  44.     }  
  45.     l.Close()  
  46. }  
  47. func BenchmarkRunWithPreCompiling(b *testing.B) {  
  48.     l := lua.NewState()  
  49.     proto, _ := CompileString(`a = 1 + 1`)  
  50.     lfunc := l.NewFunctionFromProto(proto)  
  51.     for i := 0; i < b.N; i++ {  
  52.         l.Push(lfunc) 
  53.          _ = l.PCall(0, lua.MultRet, nil)  
  54.     }  
  55.     l.Close()  
  56. }  
  57. // goos: darwin  
  58. // goarch: amd64  
  59. // pkg: glua  
  60. // BenchmarkRunWithoutPreCompiling-8         100000             19392 ns/op           85626 B/op         67 allocs/op  
  61. // BenchmarkRunWithPreCompiling-8           1000000              1162 ns/op            2752 B/op          8 allocs/op  
  62. // PASS  
  63. // ok      glua    3.328s 

虛擬機實例池

在同份 Lua 代碼被執(zhí)行的場景下,除了可使用提前編譯優(yōu)化性能外,我們還可以引入虛擬機實例池。

因為新建一個 Lua 虛擬機會涉及到大量的內存分配操作,如果采用每次運行都重新創(chuàng)建和銷毀的方式的話,將消耗大量的資源。引入虛擬機實例池,能夠復用虛擬機,減少不必要的開銷。 

 
 
 
 
  1. func BenchmarkRunWithoutPool(b *testing.B) {  
  2.     for i := 0; i < b.N; i++ {  
  3.         l := lua.NewState()  
  4.         _ = l.DoString(`a = 1 + 1`)  
  5.         l.Close()  
  6.     }  
  7. }  
  8. func BenchmarkRunWithPool(b *testing.B) {  
  9.     pool := newVMPool(nil, 100)  
  10.     for i := 0; i < b.N; i++ {  
  11.         l := pool.get()  
  12.         _ = l.DoString(`a = 1 + 1`)  
  13.         pool.put(l)  
  14.     }  
  15. }  
  16. // goos: darwin  
  17. // goarch: amd64  
  18. // pkg: glua  
  19. // BenchmarkRunWithoutPool-8          10000            129557 ns/op          262599 B/op        826 allocs/op  
  20. // BenchmarkRunWithPool-8            100000             19320 ns/op           85626 B/op         67 allocs/op  
  21. // PASS  
  22. // ok      glua    3.467s 

Benchmark 結果顯示,虛擬機實例池的確能夠減少很多內存分配操作。

下面給出了 README 提供的實例池實現(xiàn),但注意到該實現(xiàn)在初始狀態(tài)時,并未創(chuàng)建足夠多的虛擬機實例(初始時,實例數(shù)為0),以及存在 slice 的動態(tài)擴容問題,這都是值得改進的地方。 

 
 
 
 
  1. type lStatePool struct {  
  2.     m     sync.Mutex  
  3.     saved []*lua.LState  
  4. }  
  5. func (pl *lStatePool) Get() *lua.LState {  
  6.     pl.m.Lock()  
  7.     defer pl.m.Unlock()  
  8.     n := len(pl.saved)  
  9.     if n == 0 {  
  10.         return pl.New()  
  11.     }  
  12.     x := pl.saved[n-1]  
  13.     plpl.saved = pl.saved[0 : n-1]  
  14.     return x  
  15. }  
  16. func (pl *lStatePool) New() *lua.LState {  
  17.     L := lua.NewState()  
  18.     // setting the L up here.  
  19.     // load scripts, set global variables, share channels, etc...  
  20.     return L  
  21. }  
  22. func (pl *lStatePool) Put(L *lua.LState) {  
  23.     pl.m.Lock()  
  24.     defer pl.m.Unlock()  
  25.     pl.saved = append(pl.saved, L)  
  26. }  
  27. func (pl *lStatePool) Shutdown() {  
  28.     for _, L := range pl.saved {  
  29.         L.Close()  
  30.     }  
  31. }  
  32. // Global LState pool  
  33. var luaPool = &lStatePool{  
  34.     saved: make([]*lua.LState, 0, 4),  

模塊調用

gopher-lua 支持 Lua 調用 Go 模塊,個人覺得,這是一個非常令人振奮的功能點,因為在 Golang 程序開發(fā)中,我們可能設計出許多常用的模塊,這種跨語言調用的機制,使得我們能夠對代碼、工具進行復用。

當然,除此之外,也存在 Go 調用 Lua 模塊,但個人感覺后者是沒啥必要的,所以在這里并沒有涉及后者的內容。 

 
 
 
 
  1. package main  
  2. import (  
  3.     "fmt"  
  4.     lua "github.com/yuin/gopher-lua"  
  5. )  
  6. const source = `  
  7. local m = require("gomodule")  
  8. m.goFunc()  
  9. print(m.name)  
  10. func main() {  
  11.     L := lua.NewState()  
  12.     defer L.Close()  
  13.     L.PreloadModule("gomodule", load)  
  14.     if err := L.DoString(source); err != nil {  
  15.         panic(err)  
  16.     }  
  17. }  
  18. func load(L *lua.LState) int {  
  19.     mod := L.SetFuncs(L.NewTable(), exports)  
  20.     L.SetField(mod, "name", lua.LString("gomodule"))  
  21.     L.Push(mod)  
  22.     return 1  
  23. }  
  24. var exports = map[string]lua.LGFunction{  
  25.     "goFunc": goFunc,  
  26. }  
  27. func goFunc(L *lua.LState) int {  
  28.     fmt.Println("golang")  
  29.     return 0  
  30. }  
  31. // golang  
  32. // gomodule 

變量污染

當我們使用實例池減少開銷時,會引入另一個棘手的問題:由于同一個虛擬機可能會被多次執(zhí)行同樣的 Lua 代碼,進而變動了其中的全局變量。如果代碼邏輯依賴于全局變量,那么可能會出現(xiàn)難以預測的運行結果(這有點數(shù)據庫隔離性中的“不可重復讀”的味道)。

全局變量

如果我們需要限制 Lua 代碼只能使用局部變量,那么站在這個出發(fā)點上,我們需要對全局變量做出限制。那問題來了,該如何實現(xiàn)呢?

我們知道,Lua 是編譯成字節(jié)碼,再被解釋執(zhí)行的。那么,我們可以在編譯字節(jié)碼的階段中,對全局變量的使用作出限制。在查閱完 Lua 虛擬機指令后,發(fā)現(xiàn)涉及到全局變量的指令有兩條:GETGLOBAL(Opcode 5)和 SETGLOBAL(Opcode 7)。

到這里,已經有了大致的思路:我們可通過判斷字節(jié)碼是否含有 GETGLOBAL 和 SETGLOBAL 進而限制代碼的全局變量的使用。至于字節(jié)碼的獲取,可通過調用 CompileString(...) 和 CompileFile(...) ,得到 Lua 代碼的 FunctionProto ,而其中的 Code 屬性即為字節(jié)碼 slice,類型為 []uint32 。

在虛擬機實現(xiàn)代碼中,我們可以找到一個根據字節(jié)碼輸出對應 OpCode 的工具函數(shù)。 

 
 
 
 
  1. // 獲取對應指令的 OpCode  
  2. func opGetOpCode(inst uint32) int {  
  3.     return int(inst >> 26)  

有了這個工具函數(shù),我們即可實現(xiàn)對全局變量的檢查。 

 
 
 
 
  1. package main  
  2. // ...  
  3. func CheckGlobal(proto *lua.FunctionProto) error {  
  4.     for _, code := range proto.Code {  
  5.         switch opGetOpCode(code) {  
  6.         case lua.OP_GETGLOBAL:  
  7.             return errors.New("not allow to access global")  
  8.         case lua.OP_SETGLOBAL:  
  9.             return errors.New("not allow to set global")  
  10.         }  
  11.     }  
  12.     // 對嵌套函數(shù)進行全局變量的檢查  
  13.     for _, nestedProto := range proto.FunctionPrototypes {  
  14.         if err := CheckGlobal(nestedProto); err != nil {  
  15.             return err  
  16.         }  
  17.     }  
  18.     return nil  
  19. }  
  20. func TestCheckGetGlobal(t *testing.T) {  
  21.     l := lua.NewState()  
  22.     proto, _ := CompileString(`print(_G)`)  
  23.     if err := CheckGlobal(proto); err == nil {  
  24.         t.Fail()  
  25.     }  
  26.     l.Close()  
  27. }  
  28. func TestCheckSetGlobal(t *testing.T) {  
  29.     l := lua.NewState()  
  30.     proto, _ := CompileString(`_G = {}`)  
  31.     if err := CheckGlobal(proto); err == nil {  
  32.         t.Fail()  
  33.     }  
  34.     l.Close()  

模塊

除變量可能被污染外,導入的 Go 模塊也有可能在運行期間被篡改。因此,我們需要一種機制,確保導入到虛擬機的模塊不被篡改,即導入的對象是只讀的。

在查閱相關博客后,我們可以對 Table 的 __newindex 方法的修改,將模塊設置為只讀模式。 

 
 
 
 
  1. package main  
  2. import (  
  3.     "fmt"  
  4.     "github.com/yuin/gopher-lua"  
  5. )  
  6. // 設置表為只讀  
  7. func SetReadOnly(l *lua.LState, table *lua.LTable) *lua.LUserData {  
  8.     ud := l.NewUserData()  
  9.     mt := l.NewTable()  
  10.     // 設置表中域的指向為 table  
  11.     l.SetField(mt, "__index", table)  
  12.     // 限制對表的更新操作  
  13.     l.SetField(mt, "__newindex", l.NewFunction(func(state *lua.LState) int {  
  14.         state.RaiseError("not allow to modify table")  
  15.         return 0  
  16.     }))  
  17.     ud.Metatable = mt  
  18.     return ud  
  19. }  
  20. func load(l *lua.LState) int {  
  21.     mod := l.SetFuncs(l.NewTable(), exports)  
  22.     l.SetField(mod, "name", lua.LString("gomodule"))  
  23.     // 設置只讀  
  24.     l.Push(SetReadOnly(l, mod))  
  25.     return 1  
  26. }  
  27. var exports = map[string]lua.LGFunction{  
  28.     "goFunc": goFunc,  
  29. }  
  30. func goFunc(l *lua.LState) int {  
  31.     fmt.Println("golang")  
  32.     return 0  
  33. }  
  34. func main() {  
  35.     l := lua.NewState()  
  36.     l.PreloadModule("gomodule", load)  
  37.     // 嘗試修改導入的模塊  
  38.     if err := l.DoString(`local m = require("gomodule");m.name = "hello world"`); err != nil {  
  39.         fmt.Println(err)  
  40.     }  
  41.     l.Close()  
  42. }  
  43. // :1: not allow to modify table 

寫在***

Golang 和 Lua 的融合,開闊了我的視野:原來靜態(tài)語言和動態(tài)語言還能這么融合,靜態(tài)語言的運行高效率,配合動態(tài)語言的開發(fā)高效率,想想都興奮(逃。

在網上找了很久,發(fā)現(xiàn)并沒有關于 Go-Lua 的技術分享,只找到了一篇稍微有點聯(lián)系的文章(京東三級列表頁持續(xù)架構優(yōu)化 — Golang + Lua (OpenResty) ***實踐),且在這篇文章中, Lua 還是跑在 C 上的。由于信息的缺乏以及本人(學生黨)開發(fā)經驗不足的原因,并不能很好地評價該方案在實際生產中的可行性。因此,本篇文章也只能當作“閑文”了,哈哈。


本文標題:當Go遇上了Lua,會發(fā)生什么
瀏覽地址:http://www.5511xx.com/article/dhgisod.html