中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang和Lua相遇會發生什么

發布時間:2021-11-09 16:31:08 來源:億速云 閱讀:114 作者:iii 欄目:編程語言

這篇文章主要講解了“Golang和Lua相遇會發生什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Golang和Lua相遇會發生什么”吧!

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

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

Hello World

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

package main
import (
"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

提前編譯

在查看上述 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 代碼將被執行多次(如在 http server 中,每次請求將執行相同 Lua 代碼)的場景下,如果我們能夠對代碼進行提前編譯,那么應該能夠減少 parse 和 compile 的開銷(如果這屬于 hotpath 代碼)。根據 Benchmark 結果,提前編譯確實能夠減少不必要的開銷。

package glua_test
import (
"bufio"
"os"
"strings"
lua "github.com/yuin/gopher-lua"
"github.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 代碼被執行的場景下,除了可使用提前編譯優化性能外,我們還可以引入虛擬機實例池。

因為新建一個 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 pool
var luaPool = &lStatePool{
    saved: make([]*lua.LState, 0, 4),
}

模塊調用

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

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

package main
import (
"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 的工具函數。

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

有了這個工具函數,我們即可實現對全局變量的檢查。

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")
}
}
// 對嵌套函數進行全局變量的檢查
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()
}

模塊

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

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

package main
import (
"fmt"
"github.com/yuin/gopher-lua"
)
// 設置表為只讀
func SetReadOnly(l *lua.LState, table *lua.LTable) *lua.LUserData {
ud := l.NewUserData()
mt := l.NewTable()
// 設置表中域的指向為 table
l.SetField(mt, "__index", table)
// 限制對表的更新操作
l.SetField(mt, "__newindex", l.NewFunction(func(state *lua.LState) int {
state.RaiseError("not allow to modify table")
return 0
}))
ud.Metatable = mt
return ud
}
func load(l *lua.LState) int {
mod := l.SetFuncs(l.NewTable(), exports)
l.SetField(mod, "name", lua.LString("gomodule"))
// 設置只讀
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)
    // 嘗試修改導入的模塊
if err := l.DoString(`local m = require("gomodule");m.name = "hello world"`); err != nil {
fmt.Println(err)
}
l.Close()
}
// <string>:1: not allow to modify table

感謝各位的閱讀,以上就是“Golang和Lua相遇會發生什么”的內容了,經過本文的學習后,相信大家對Golang和Lua相遇會發生什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

凤冈县| 东山县| 巴彦淖尔市| 普洱| 甘南县| 贡觉县| 广元市| 错那县| 嘉祥县| 新巴尔虎左旗| 昔阳县| 常德市| 朔州市| 兴国县| 民丰县| 二手房| 吉林省| 浙江省| 五河县| 屏南县| 双城市| 平遥县| 克拉玛依市| 读书| 时尚| 阿坝县| 岳阳县| 新乡市| 常山县| 加查县| 平舆县| 孟连| 临颍县| 依兰县| 赤水市| 沂水县| 天祝| 北流市| 巨野县| 融水| 白城市|