perf(lua): LuaContext 对象池化优化

- 添加 luaContextPool 复用 LuaContext 对象
- 新增 AcquireContext 函数从池中获取
- Release 方法重置所有可变状态防止污染
- 添加状态隔离测试和多次复用测试
- 添加池化基准测试

降低 GC 压力,减少高频请求下的对象分配

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-16 11:09:26 +08:00
parent cd807e43aa
commit 6dd651af5f
3 changed files with 114 additions and 10 deletions

View File

@ -2,6 +2,8 @@
package lua
import (
"sync"
"github.com/valyala/fasthttp"
)
@ -21,14 +23,28 @@ type LuaContext struct {
Exited bool
}
// NewContext 创建请求上下文
// luaContextPool LuaContext 对象池
var luaContextPool = sync.Pool{
New: func() interface{} {
return &LuaContext{
Variables: make(map[string]string),
}
},
}
// AcquireContext 从池中获取 LuaContext
func AcquireContext(engine *LuaEngine, req *fasthttp.RequestCtx) *LuaContext {
lc := luaContextPool.Get().(*LuaContext)
lc.Engine = engine
lc.RequestCtx = req
lc.Phase = PhaseInit
// Variables 和 OutputBuffer 已在 Release 中重置
return lc
}
// NewContext 创建请求上下文(从池中获取)
func NewContext(engine *LuaEngine, req *fasthttp.RequestCtx) *LuaContext {
return &LuaContext{
Engine: engine,
RequestCtx: req,
Variables: make(map[string]string),
Phase: PhaseInit,
}
return AcquireContext(engine, req)
}
// InitCoroutine 初始化协程
@ -99,14 +115,24 @@ func (c *LuaContext) Exit(code int) {
c.RequestCtx.SetStatusCode(code)
}
// Release 释放资源
// Release 释放资源并放回池中
func (c *LuaContext) Release() {
if c.Coroutine != nil {
c.Coroutine.Close()
c.Coroutine = nil
}
c.Variables = nil
c.OutputBuffer = nil
// 重置所有可变状态,防止请求间污染
for k := range c.Variables {
delete(c.Variables, k)
}
c.OutputBuffer = c.OutputBuffer[:0]
c.Phase = PhaseInit
c.Exited = false
c.Engine = nil
c.RequestCtx = nil
luaContextPool.Put(c)
}
// FlushOutput 刷新输出到响应

View File

@ -25,6 +25,24 @@ func BenchmarkCoroutineCreation(b *testing.B) {
}
}
// BenchmarkLuaContextPool 测试 LuaContext 池化开销
func BenchmarkLuaContextPool(b *testing.B) {
engine, err := NewEngine(DefaultConfig())
if err != nil {
b.Fatal(err)
}
defer engine.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
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())

View File

@ -94,6 +94,66 @@ func TestLuaContextFlushOutput(t *testing.T) {
assert.NotNil(t, ctx.OutputBuffer)
}
// TestLuaContextPoolStateIsolation 测试池化 context 请求间无状态污染
func TestLuaContextPoolStateIsolation(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
// 第一次使用:设置变量、输出、阶段、退出标记
ctx1 := NewContext(engine, nil)
ctx1.SetVariable("key1", "value1")
ctx1.SetVariable("key2", "value2")
ctx1.Write([]byte("hello"))
ctx1.SetPhase(PhaseAccess)
ctx1.Exited = true
// 释放回池
ctx1.Release()
// 第二次使用:从池中获取(可能是同一个对象)
ctx2 := NewContext(engine, nil)
// 验证无状态污染
assert.Equal(t, PhaseInit, ctx2.Phase, "Phase should be reset to PhaseInit")
assert.False(t, ctx2.Exited, "Exited should be reset to false")
assert.Empty(t, ctx2.OutputBuffer, "OutputBuffer should be empty")
assert.Empty(t, ctx2.Variables, "Variables map should be empty")
// 验证旧的 key 不存在
_, ok := ctx2.GetVariable("key1")
assert.False(t, ok, "key1 should not exist after release")
_, ok = ctx2.GetVariable("key2")
assert.False(t, ok, "key2 should not exist after release")
ctx2.Release()
}
// TestLuaContextPoolMultipleReuse 测试多次复用
func TestLuaContextPoolMultipleReuse(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
// 循环多次 release/acquire验证状态始终正确
for i := 0; i < 100; i++ {
ctx := NewContext(engine, nil)
ctx.SetVariable("iter", "val")
ctx.Write([]byte("data"))
ctx.SetPhase(PhaseLog)
ctx.Exited = true
ctx.Release()
}
// 最后一次获取,验证状态干净
ctx := NewContext(engine, nil)
assert.Equal(t, PhaseInit, ctx.Phase)
assert.False(t, ctx.Exited)
assert.Empty(t, ctx.OutputBuffer)
assert.Empty(t, ctx.Variables)
ctx.Release()
}
// TestLuaContextExecute 测试 Lua 执行
func TestLuaContextExecute(t *testing.T) {
engine, err := NewEngine(DefaultConfig())