|
| 1 | +# Lua 字节码功能 |
| 2 | + |
| 3 | +## 概述 |
| 4 | + |
| 5 | +AutoGo ScriptEngine 现已支持 Lua 字节码功能,允许预编译 Lua 脚本并重复执行,提高执行效率。 |
| 6 | + |
| 7 | +## 重要说明 |
| 8 | + |
| 9 | +⚠️ **gopher-lua 的字节码格式与标准 Lua 字节码不兼容** |
| 10 | + |
| 11 | +- gopher-lua 使用自己的字节码格式,这是 gopher-lua 特有的实现 |
| 12 | +- 标准 Lua 编译器(luac)生成的 `.luac` 文件无法在 gopher-lua 中执行 |
| 13 | +- gopher-lua 的字节码仅能在 gopher-lua 虚拟机中执行 |
| 14 | +- 字节码格式可能会随 gopher-lua 版本变化,建议保留源码 |
| 15 | + |
| 16 | +## 功能特性 |
| 17 | + |
| 18 | +### 1. 编译 Lua 源码为字节码 |
| 19 | + |
| 20 | +```go |
| 21 | +package main |
| 22 | + |
| 23 | +import ( |
| 24 | + "fmt" |
| 25 | + "log" |
| 26 | + |
| 27 | + "github.com/ZingYao/autogo_scriptengine/lua_engine" |
| 28 | +) |
| 29 | + |
| 30 | +func main() { |
| 31 | + // 初始化 Lua 引擎 |
| 32 | + engine := lua_engine.NewLuaEngine(nil) |
| 33 | + defer engine.Close() |
| 34 | + |
| 35 | + // Lua 源码 |
| 36 | + source := ` |
| 37 | + local function add(a, b) |
| 38 | + return a + b |
| 39 | + end |
| 40 | + print("结果: " .. add(3, 5)) |
| 41 | + ` |
| 42 | + |
| 43 | + // 编译源码为字节码 |
| 44 | + bytecode, err := engine.CompileString(source, "my_script") |
| 45 | + if err != nil { |
| 46 | + log.Fatalf("编译失败: %v", err) |
| 47 | + } |
| 48 | + |
| 49 | + fmt.Printf("字节码编译成功! 名称: %s\n", bytecode.GetName()) |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +### 2. 执行预编译的字节码 |
| 54 | + |
| 55 | +```go |
| 56 | +// 执行字节码 |
| 57 | +err = engine.ExecuteBytecode(bytecode) |
| 58 | +if err != nil { |
| 59 | + log.Fatalf("执行失败: %v", err) |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +### 3. 从文件编译字节码 |
| 64 | + |
| 65 | +```go |
| 66 | +// 从文件编译 |
| 67 | +bytecode, err := engine.CompileFile("scripts/main.lua") |
| 68 | +if err != nil { |
| 69 | + log.Fatalf("编译文件失败: %v", err) |
| 70 | +} |
| 71 | + |
| 72 | +// 执行 |
| 73 | +err = engine.ExecuteBytecode(bytecode) |
| 74 | +if err != nil { |
| 75 | + log.Fatalf("执行失败: %v", err) |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +### 4. 使用全局函数 |
| 80 | + |
| 81 | +```go |
| 82 | +// 使用全局引擎 |
| 83 | +engine := lua_engine.GetLuaEngine() |
| 84 | +defer engine.Close() |
| 85 | + |
| 86 | +// 全局编译函数 |
| 87 | +bytecode, err := lua_engine.CompileString("print('Hello, World!')", "hello") |
| 88 | +if err != nil { |
| 89 | + log.Fatal(err) |
| 90 | +} |
| 91 | + |
| 92 | +// 全局执行函数 |
| 93 | +err = lua_engine.ExecuteBytecode(bytecode) |
| 94 | +if err != nil { |
| 95 | + log.Fatal(err) |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +### 5. 异步执行字节码 |
| 100 | + |
| 101 | +```go |
| 102 | +// 异步执行字节码 |
| 103 | +err := engine.ExecuteBytecodeWithMode(bytecode, lua_engine.ExecuteModeAsync) |
| 104 | +if err != nil { |
| 105 | + log.Fatal(err) |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +## require 函数支持字节码模块 |
| 110 | + |
| 111 | +`require` 函数现在支持加载 `.gluac` 字节码文件: |
| 112 | + |
| 113 | +### 模块加载优先级 |
| 114 | + |
| 115 | +1. `module.gluac` - gopher-lua 字节码文件 |
| 116 | +2. `module.lua` - Lua 源码文件 |
| 117 | +3. `module/init.gluac` - 目录形式的字节码模块 |
| 118 | +4. `module/init.lua` - 目录形式的源码模块 |
| 119 | + |
| 120 | +### 使用示例 |
| 121 | + |
| 122 | +假设有以下文件结构: |
| 123 | + |
| 124 | +``` |
| 125 | +scripts/ |
| 126 | +├── utils.gluac # 预编译的字节码模块 |
| 127 | +├── utils.lua # 源码模块(备用) |
| 128 | +└── main.lua # 主脚本 |
| 129 | +``` |
| 130 | + |
| 131 | +在 `main.lua` 中: |
| 132 | + |
| 133 | +```lua |
| 134 | +-- 加载字节码模块(优先加载 .gluac 文件) |
| 135 | +local utils = require("utils") |
| 136 | + |
| 137 | +-- 使用模块功能 |
| 138 | +utils.hello() |
| 139 | +``` |
| 140 | + |
| 141 | +## 性能优势 |
| 142 | + |
| 143 | +预编译字节码的主要优势: |
| 144 | + |
| 145 | +1. **减少解析开销**:避免每次执行时的词法分析和语法分析 |
| 146 | +2. **提高启动速度**:直接加载编译后的字节码,减少启动时间 |
| 147 | +3. **适合重复执行**:在循环或高频调用场景下性能提升明显 |
| 148 | + |
| 149 | +### 性能对比示例 |
| 150 | + |
| 151 | +```go |
| 152 | +package main |
| 153 | + |
| 154 | +import ( |
| 155 | + "fmt" |
| 156 | + "time" |
| 157 | + |
| 158 | + "github.com/ZingYao/autogo_scriptengine/lua_engine" |
| 159 | +) |
| 160 | + |
| 161 | +func main() { |
| 162 | + engine := lua_engine.NewLuaEngine(nil) |
| 163 | + defer engine.Close() |
| 164 | + |
| 165 | + source := "local a = 1 + 1" |
| 166 | + |
| 167 | + // 测试直接执行 |
| 168 | + start := time.Now() |
| 169 | + for i := 0; i < 10000; i++ { |
| 170 | + engine.ExecuteString(source) |
| 171 | + } |
| 172 | + directDuration := time.Since(start) |
| 173 | + |
| 174 | + // 测试字节码执行 |
| 175 | + bytecode, _ := engine.CompileString(source) |
| 176 | + start = time.Now() |
| 177 | + for i := 0; i < 10000; i++ { |
| 178 | + engine.ExecuteBytecode(bytecode) |
| 179 | + } |
| 180 | + bytecodeDuration := time.Since(start) |
| 181 | + |
| 182 | + fmt.Printf("直接执行: %v\n", directDuration) |
| 183 | + fmt.Printf("字节码执行: %v\n", bytecodeDuration) |
| 184 | + fmt.Printf("性能提升: %.2fx\n", float64(directDuration)/float64(bytecodeDuration)) |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +## API 参考 |
| 189 | + |
| 190 | +### Bytecode 类型 |
| 191 | + |
| 192 | +```go |
| 193 | +type Bytecode struct { |
| 194 | + Proto *lua.FunctionProto // 编译后的函数原型 |
| 195 | + Name string // 字节码名称 |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +### 方法列表 |
| 200 | + |
| 201 | +| 方法 | 说明 | |
| 202 | +|------|------| |
| 203 | +| `CompileString(source string, name ...string)` | 编译源码字符串为字节码 | |
| 204 | +| `CompileFile(path string)` | 编译源码文件为字节码 | |
| 205 | +| `ExecuteBytecode(bytecode *Bytecode)` | 执行字节码 | |
| 206 | +| `ExecuteBytecodeWithMode(bytecode *Bytecode, mode ExecuteMode)` | 带模式的执行字节码 | |
| 207 | +| `bytecode.GetName()` | 获取字节码名称 | |
| 208 | +| `bytecode.GetFunctionProto()` | 获取函数原型 | |
| 209 | + |
| 210 | +### 全局函数 |
| 211 | + |
| 212 | +| 函数 | 说明 | |
| 213 | +|------|------| |
| 214 | +| `lua_engine.CompileString(source, name)` | 全局编译函数 | |
| 215 | +| `lua_engine.CompileFile(path)` | 全局编译文件函数 | |
| 216 | +| `lua_engine.ExecuteBytecode(bytecode)` | 全局执行函数 | |
| 217 | + |
| 218 | +## 最佳实践 |
| 219 | + |
| 220 | +### 1. 保留源码 |
| 221 | + |
| 222 | +由于 gopher-lua 字节码格式可能变化,建议始终保留原始源码: |
| 223 | + |
| 224 | +```go |
| 225 | +// 编译并保存字节码 |
| 226 | +bytecode, err := engine.CompileString(source, "script") |
| 227 | +if err != nil { |
| 228 | + log.Fatal(err) |
| 229 | +} |
| 230 | + |
| 231 | +// 同时保存源码和字节码 |
| 232 | +// 源码用于备份和调试 |
| 233 | +// 字节码用于生产环境 |
| 234 | +``` |
| 235 | + |
| 236 | +### 2. 使用字节码缓存 |
| 237 | + |
| 238 | +对于频繁执行的脚本,使用字节码缓存: |
| 239 | + |
| 240 | +```go |
| 241 | +// 初始化时编译所有常用脚本 |
| 242 | +var scriptCache = make(map[string]*lua_engine.Bytecode) |
| 243 | + |
| 244 | +func init() { |
| 245 | + engine := lua_engine.GetLuaEngine() |
| 246 | + |
| 247 | + // 预编译常用脚本 |
| 248 | + scripts := map[string]string{ |
| 249 | + "utils": "scripts/utils.lua", |
| 250 | + "math": "scripts/math.lua", |
| 251 | + } |
| 252 | + |
| 253 | + for name, path := range scripts { |
| 254 | + bytecode, err := engine.CompileFile(path) |
| 255 | + if err != nil { |
| 256 | + log.Printf("编译 %s 失败: %v", name, err) |
| 257 | + continue |
| 258 | + } |
| 259 | + scriptCache[name] = bytecode |
| 260 | + } |
| 261 | +} |
| 262 | +``` |
| 263 | + |
| 264 | +### 3. 错误处理 |
| 265 | + |
| 266 | +```go |
| 267 | +bytecode, err := engine.CompileString(source) |
| 268 | +if err != nil { |
| 269 | + // 编译错误,检查源码语法 |
| 270 | + log.Printf("编译错误: %v", err) |
| 271 | + return |
| 272 | +} |
| 273 | + |
| 274 | +err = engine.ExecuteBytecode(bytecode) |
| 275 | +if err != nil { |
| 276 | + // 运行时错误 |
| 277 | + log.Printf("执行错误: %v", err) |
| 278 | + return |
| 279 | +} |
| 280 | +``` |
| 281 | + |
| 282 | +## 限制和注意事项 |
| 283 | + |
| 284 | +1. **字节码格式不兼容**:gopher-lua 字节码与标准 Lua 字节码不兼容 |
| 285 | +2. **版本依赖**:字节码格式可能随 gopher-lua 版本变化 |
| 286 | +3. **调试困难**:字节码难以调试,建议开发阶段使用源码 |
| 287 | +4. **序列化限制**:gopher-lua 的 FunctionProto 不支持直接序列化 |
| 288 | + |
| 289 | +## 示例代码 |
| 290 | + |
| 291 | +完整的示例代码请参考: |
| 292 | + |
| 293 | +- [examples/lua_engine/bytecode/main.go](../../examples/lua_engine/bytecode/main.go) |
| 294 | + |
| 295 | +## 更新日志 |
| 296 | + |
| 297 | +### v1.0.0 (2026-04-06) |
| 298 | + |
| 299 | +- 添加 `CompileString` 方法支持编译源码字符串 |
| 300 | +- 添加 `CompileFile` 方法支持编译源码文件 |
| 301 | +- 添加 `ExecuteBytecode` 方法支持执行字节码 |
| 302 | +- 添加 `ExecuteBytecodeWithMode` 方法支持异步执行 |
| 303 | +- 修改 `require` 函数支持加载 `.gluac` 字节码文件 |
| 304 | +- 添加全局编译和执行函数 |
0 commit comments