lolly/internal/lua/lua_bench_test.go
xfy 6c7cf73c87 refactor(lua): replace single LState with LState pool architecture
Replace the single LState + coroutine model with an LState pool to
eliminate concurrent map read/write issues in gopher-lua. Each request
now gets a completely independent LState with its own Global table.

Key changes:
- Add LStatePool for managing pooled LState instances
- Remove shared Engine.L and coroutine-based execution
- Simplify coroutine.go: remove yield handling, use direct PCall
- Remove ngxRegisterMu lock (no longer needed with isolated LStates)
- Update config.go: add LStatePoolInitialSize/MaxSize settings
- Update tests to work with the new architecture

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 10:38:10 +08:00

277 lines
5.9 KiB
Go

package lua
import (
"testing"
"time"
glua "github.com/yuin/gopher-lua"
)
// BenchmarkCoroutineCreation 测试协程创建开销
func BenchmarkCoroutineCreation(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
b.ResetTimer()
for b.Loop() {
coro, err := engine.NewCoroutine(nil)
if err != nil {
b.Fatal(err)
}
engine.releaseCoroutine(coro)
}
}
// BenchmarkLuaContextPool 测试 LuaContext 池化开销
func BenchmarkLuaContextPool(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
b.ResetTimer()
for b.Loop() {
ctx := NewContext(engine, nil)
ctx.SetVariable("key", "value")
ctx.Write([]byte("hello"))
ctx.SetPhase(PhaseContent)
ctx.Release()
}
}
// BenchmarkBytecodeCompilation 测试字节码编译开销
func BenchmarkBytecodeCompilation(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
script := `
local x = 1 + 2
local y = x * 3
local z = "hello " .. "world"
if y > 5 then
return z
end
return x
`
b.ResetTimer()
for b.Loop() {
_, err := engine.CodeCache().GetOrCompileInline(script)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkSharedDictSetGet 测试共享字典读写开销
func BenchmarkSharedDictSetGet(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
dict := engine.CreateSharedDict("bench", 10000)
b.ResetTimer()
for b.Loop() {
dict.Set("key", "value", 0)
dict.Get("key")
}
}
// BenchmarkTimerCallbackThroughput 测试定时器回调吞吐量
func BenchmarkTimerCallbackThroughput(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
manager := engine.TimerManager()
callback := engine.GetLStateForTest().NewFunction(func(L *glua.LState) int {
return 0
})
b.ResetTimer()
for b.Loop() {
manager.At(1*time.Millisecond, callback, nil)
}
b.StopTimer()
// 等待所有定时器完成
manager.WaitAll(5 * time.Second)
}
// BenchmarkTimerCallbackWithLuaExecution 测试带 Lua 执行的定时器回调
func BenchmarkTimerCallbackWithLuaExecution(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
L := engine.GetLStateForTest()
ngx := L.NewTable()
L.SetGlobal("ngx", ngx)
RegisterTimerAPI(L, engine.TimerManager(), ngx)
// 注册共享字典 API 供回调使用
RegisterSharedDictAPI(L, engine.SharedDictManager(), ngx)
// 简单的无 upvalue 回调
script := `
ngx.timer.at(0.001, function()
-- empty callback
end)
`
b.ResetTimer()
for b.Loop() {
if err := L.DoString(script); err != nil {
b.Fatal(err)
}
}
b.StopTimer()
// 等待所有回调执行完成
time.Sleep(100 * time.Millisecond)
}
// BenchmarkUpvalueDetection 测试 upvalue 检测开销
func BenchmarkUpvalueDetection(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
L := engine.GetLStateForTest()
ngx := L.NewTable()
L.SetGlobal("ngx", ngx)
RegisterTimerAPI(L, engine.TimerManager(), ngx)
b.ResetTimer()
for b.Loop() {
// 尝试注册带有 upvalue 的回调(应该被拒绝)
err := L.DoString(`
local x = 42
local ok, err = ngx.timer.at(1, function() return x end)
`)
if err != nil {
// 错误可能被 Lua 内部处理,检查返回值
_ = err
}
}
}
// BenchmarkTimerGracefulShutdown 测试优雅关闭开销
func BenchmarkTimerGracefulShutdown(b *testing.B) {
for b.Loop() {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
manager := engine.TimerManager()
callback := engine.GetLStateForTest().NewFunction(func(L *glua.LState) int {
return 0
})
// 创建一些定时器
for range 10 {
manager.At(1*time.Millisecond, callback, nil)
}
// 关闭引擎(包含优雅关闭)
engine.Close()
}
}
// BenchmarkLuaContextPoolReuse 测试 LuaContext 池复用率。
// 验证多次获取/释放后池能否有效复用对象,减少分配。
func BenchmarkLuaContextPoolReuse(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
// 模拟一次完整的请求生命周期
ctx := NewContext(engine, nil)
ctx.SetVariable("uri", "/test")
ctx.SetVariable("method", "GET")
ctx.SetPhase(PhaseContent)
ctx.Write([]byte("response body"))
ctx.FlushOutput()
ctx.Release()
}
}
// BenchmarkLuaCoroutinePoolThroughput 测试协程池吞吐量。
// 验证协程池在高频率创建/销毁场景下的复用效果。
func BenchmarkLuaCoroutinePoolThroughput(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
coro, err := engine.NewCoroutine(nil)
if err != nil {
continue
}
coro.Close()
}
})
}
// BenchmarkLuaTablePool 测试 Lua table 对象池性能。
// 验证 table 创建和池复用在频繁 table 操作场景下的表现。
func BenchmarkLuaTablePool(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
L := engine.GetLStateForTest()
b.Run("NewTable_NoPool", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
t := L.NewTable()
t.RawSetString("key1", glua.LString("value1"))
t.RawSetString("key2", glua.LString("value2"))
t.RawSetString("key3", glua.LNumber(42))
}
})
b.Run("SharedDict_AsPool", func(b *testing.B) {
// 共享字典底层使用对象池存储条目
dict := engine.CreateSharedDict("bench_pool", 1000)
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
dict.Set("key", "value_with_pool", 0)
dict.Get("key")
dict.Delete("key")
}
})
}