Try to optimize route compile benchmark, optimize luarouter

This commit is contained in:
Sky Johnson 2025-05-24 22:28:20 -05:00
parent 13cb83b7a7
commit 82470b35a0
2 changed files with 144 additions and 26 deletions

View File

@ -52,6 +52,15 @@ type LuaRouter struct {
// Middleware tracking for path hierarchy
middlewareFiles map[string][]string // path -> middleware file paths
// New caching fields
middlewareCache map[string][]byte // path -> content
sourceCache map[string][]byte // combined source cache key -> compiled bytecode
sourceMtimes map[string]time.Time // track modification times
// Shared Lua state for compilation
compileState *luajit.State
compileStateMu sync.Mutex // Protect concurrent access to Lua state
}
// node represents a node in the routing trie
@ -75,6 +84,12 @@ func NewLuaRouter(routesDir string) (*LuaRouter, error) {
return nil, errors.New("routes path is not a directory")
}
// Create shared Lua state
compileState := luajit.New()
if compileState == nil {
return nil, errors.New("failed to create Lua compile state")
}
r := &LuaRouter{
routesDir: routesDir,
routes: make(map[string]*node),
@ -82,6 +97,10 @@ func NewLuaRouter(routesDir string) (*LuaRouter, error) {
middlewareFiles: make(map[string][]string),
routeCache: fastcache.New(defaultRouteMaxBytes),
bytecodeCache: fastcache.New(defaultBytecodeMaxBytes),
middlewareCache: make(map[string][]byte),
sourceCache: make(map[string][]byte),
sourceMtimes: make(map[string]time.Time),
compileState: compileState,
}
methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"}
@ -221,18 +240,86 @@ func (r *LuaRouter) compileWithMiddleware(n *node, urlPath, scriptPath string) e
return nil
}
// Collect middleware for this path (cascading from root)
middlewareChain := r.getMiddlewareChain(urlPath)
// Check if we need to recompile by comparing modification times
sourceKey := r.getSourceCacheKey(urlPath, scriptPath)
needsRecompile := false
// Read and combine all source files
// Check handler modification time
handlerInfo, err := os.Stat(scriptPath)
if err != nil {
n.err = err
return err
}
lastCompiled, exists := r.sourceMtimes[sourceKey]
if !exists || handlerInfo.ModTime().After(lastCompiled) {
needsRecompile = true
}
// Check middleware modification times
if !needsRecompile {
middlewareChain := r.getMiddlewareChain(urlPath)
for _, mwPath := range middlewareChain {
mwInfo, err := os.Stat(mwPath)
if err != nil {
n.err = err
return err
}
if mwInfo.ModTime().After(lastCompiled) {
needsRecompile = true
break
}
}
}
// Use cached bytecode if available and fresh
if !needsRecompile {
if bytecode, exists := r.sourceCache[sourceKey]; exists {
bytecodeKey := getBytecodeKey(scriptPath)
r.bytecodeCache.Set(bytecodeKey, bytecode)
return nil
}
}
// Build combined source
combinedSource, err := r.buildCombinedSource(urlPath, scriptPath)
if err != nil {
n.err = err
return err
}
// Compile combined source using shared state
r.compileStateMu.Lock()
bytecode, err := r.compileState.CompileBytecode(combinedSource, scriptPath)
r.compileStateMu.Unlock()
if err != nil {
n.err = err
return err
}
// Cache everything
bytecodeKey := getBytecodeKey(scriptPath)
r.bytecodeCache.Set(bytecodeKey, bytecode)
r.sourceCache[sourceKey] = bytecode
r.sourceMtimes[sourceKey] = time.Now()
n.err = nil
return nil
}
// buildCombinedSource builds the combined middleware + handler source
func (r *LuaRouter) buildCombinedSource(urlPath, scriptPath string) (string, error) {
var combinedSource strings.Builder
// Get middleware chain
middlewareChain := r.getMiddlewareChain(urlPath)
// Add middleware in order
for _, mwPath := range middlewareChain {
content, err := os.ReadFile(mwPath)
content, err := r.getFileContent(mwPath)
if err != nil {
n.err = err
return err
return "", err
}
combinedSource.WriteString("-- Middleware: ")
combinedSource.WriteString(mwPath)
@ -242,36 +329,51 @@ func (r *LuaRouter) compileWithMiddleware(n *node, urlPath, scriptPath string) e
}
// Add main handler
content, err := os.ReadFile(scriptPath)
content, err := r.getFileContent(scriptPath)
if err != nil {
n.err = err
return err
return "", err
}
combinedSource.WriteString("-- Handler: ")
combinedSource.WriteString(scriptPath)
combinedSource.WriteString("\n")
combinedSource.Write(content)
// Compile combined source
state := luajit.New()
if state == nil {
compileErr := errors.New("failed to create Lua state")
n.err = compileErr
return compileErr
}
defer state.Close()
return combinedSource.String(), nil
}
bytecode, err := state.CompileBytecode(combinedSource.String(), scriptPath)
// getFileContent reads file content with caching
func (r *LuaRouter) getFileContent(path string) ([]byte, error) {
// Check cache first
if content, exists := r.middlewareCache[path]; exists {
// Verify file hasn't changed
info, err := os.Stat(path)
if err == nil {
if cachedTime, exists := r.sourceMtimes[path]; exists && !info.ModTime().After(cachedTime) {
return content, nil
}
}
}
// Read from disk
content, err := os.ReadFile(path)
if err != nil {
n.err = err
return err
return nil, err
}
bytecodeKey := getBytecodeKey(scriptPath)
r.bytecodeCache.Set(bytecodeKey, bytecode)
// Cache it
r.middlewareCache[path] = content
r.sourceMtimes[path] = time.Now()
n.err = nil
return nil
return content, nil
}
// getSourceCacheKey generates a unique key for combined source
func (r *LuaRouter) getSourceCacheKey(urlPath, scriptPath string) string {
middlewareChain := r.getMiddlewareChain(urlPath)
var keyParts []string
keyParts = append(keyParts, middlewareChain...)
keyParts = append(keyParts, scriptPath)
return strings.Join(keyParts, "|")
}
// getMiddlewareChain returns middleware files that apply to the given path
@ -564,6 +666,9 @@ func (r *LuaRouter) Refresh() error {
r.failedRoutes = make(map[string]*RouteError)
r.middlewareFiles = make(map[string][]string)
r.middlewareCache = make(map[string][]byte)
r.sourceCache = make(map[string][]byte)
r.sourceMtimes = make(map[string]time.Time)
err := r.buildRoutes()
@ -587,10 +692,23 @@ func (r *LuaRouter) ReportFailedRoutes() []*RouteError {
return result
}
// ClearCache clears both the route and bytecode caches
// ClearCache clears all caches
func (r *LuaRouter) ClearCache() {
r.routeCache.Reset()
r.bytecodeCache.Reset()
r.middlewareCache = make(map[string][]byte)
r.sourceCache = make(map[string][]byte)
r.sourceMtimes = make(map[string]time.Time)
}
// Close cleans up the router and its resources
func (r *LuaRouter) Close() {
r.compileStateMu.Lock()
if r.compileState != nil {
r.compileState.Close()
r.compileState = nil
}
r.compileStateMu.Unlock()
}
// GetCacheStats returns statistics about the cache

View File

@ -298,7 +298,7 @@ func BenchmarkRouteCompilation(b *testing.B) {
routesDir := filepath.Join(tempDir, "routes")
os.MkdirAll(routesDir, 0755)
for b.Loop() {
for i := 0; i < b.N; i++ {
b.StopTimer()
os.RemoveAll(routesDir)
os.MkdirAll(routesDir, 0755)