package luajit_bench import ( "testing" luajit "git.sharkk.net/Sky/LuaJIT-to-Go" ) var benchCases = []struct { name string code string }{ { name: "SimpleAddition", code: `return 1 + 1`, }, { name: "LoopSum", code: ` local sum = 0 for i = 1, 1000 do sum = sum + i end return sum `, }, { name: "FunctionCall", code: ` local result = 0 for i = 1, 100 do result = result + i end return result `, }, { name: "TableCreation", code: ` local t = {} for i = 1, 100 do t[i] = i * 2 end return t[50] `, }, { name: "StringOperations", code: ` local s = "hello" for i = 1, 10 do s = s .. " world" end return #s `, }, } func BenchmarkLuaDirectExecution(b *testing.B) { for _, bc := range benchCases { b.Run(bc.name, func(b *testing.B) { L := luajit.New() if L == nil { b.Fatal("Failed to create Lua state") } defer L.Close() defer L.Cleanup() // First verify we can execute the code if err := L.DoString(bc.code); err != nil { b.Fatalf("Failed to execute test code: %v", err) } b.ResetTimer() TrackMemoryUsage(b, "direct-"+bc.name, func() { for i := 0; i < b.N; i++ { // Execute string and get results nresults, err := L.Execute(bc.code) if err != nil { b.Fatalf("Failed to execute code: %v", err) } L.Pop(nresults) // Clean up any results } }) }) } } func BenchmarkLuaBytecodeExecution(b *testing.B) { // First compile all bytecode bytecodes := make(map[string][]byte) for _, bc := range benchCases { L := luajit.New() if L == nil { b.Fatal("Failed to create Lua state") } defer L.Cleanup() bytecode, err := L.CompileBytecode(bc.code, bc.name) if err != nil { L.Close() b.Fatalf("Error compiling bytecode for %s: %v", bc.name, err) } bytecodes[bc.name] = bytecode L.Close() } for _, bc := range benchCases { b.Run(bc.name, func(b *testing.B) { L := luajit.New() if L == nil { b.Fatal("Failed to create Lua state") } defer L.Close() defer L.Cleanup() bytecode := bytecodes[bc.name] // First verify we can execute the bytecode if err := L.LoadAndRunBytecodeWithResults(bytecode, bc.name, 1); err != nil { b.Fatalf("Failed to execute test bytecode: %v", err) } L.Pop(1) // Clean up the result b.ResetTimer() b.SetBytes(int64(len(bytecode))) // Track bytecode size in benchmarks TrackMemoryUsage(b, "bytecode-"+bc.name, func() { for i := 0; i < b.N; i++ { if err := L.LoadAndRunBytecode(bytecode, bc.name); err != nil { b.Fatalf("Error executing bytecode: %v", err) } } }) }) } } // BenchmarkBatchTableOperations compares batch vs individual table operations func BenchmarkBatchTableOperations(b *testing.B) { testData := map[string]any{ "name": "testapp", "version": "1.0.0", "port": 8080, "debug": true, "timeout": 30.5, "tags": []string{"web", "api", "service"}, "limits": []int{100, 200, 300, 400, 500}, } b.Run("Individual", func(b *testing.B) { L := luajit.New() defer L.Close() defer L.Cleanup() TrackMemoryUsage(b, "table-individual", func() { for i := 0; i < b.N; i++ { tb := L.NewTableBuilder() for k, v := range testData { switch val := v.(type) { case string: tb.SetString(k, val) case int: tb.SetNumber(k, float64(val)) case bool: tb.SetBool(k, val) case float64: tb.SetNumber(k, val) default: tb.SetTable(k, v) } } tb.Build() L.Pop(1) } }) }) b.Run("Batch", func(b *testing.B) { L := luajit.New() defer L.Close() defer L.Cleanup() TrackMemoryUsage(b, "table-batch", func() { for i := 0; i < b.N; i++ { tb := L.NewTableBuilder() tb.BatchBuild(testData) tb.Build() L.Pop(1) } }) }) b.Run("BatchTableBuilder", func(b *testing.B) { L := luajit.New() defer L.Close() defer L.Cleanup() TrackMemoryUsage(b, "table-batch-builder", func() { for i := 0; i < b.N; i++ { btb := L.NewBatchTableBuilder() for k, v := range testData { switch val := v.(type) { case string: btb.SetString(k, val) case int: btb.SetNumber(k, float64(val)) case bool: btb.SetBool(k, val) case float64: btb.SetNumber(k, val) default: btb.SetTable(k, v) } } btb.Build() L.Pop(1) } }) }) } // BenchmarkBatchArrayOperations compares different array creation methods func BenchmarkBatchArrayOperations(b *testing.B) { intArray := make([]int, 50) stringArray := make([]string, 50) floatArray := make([]float64, 50) boolArray := make([]bool, 50) for i := range 50 { intArray[i] = i * 2 stringArray[i] = "item" + string(rune('0'+i%10)) floatArray[i] = float64(i) * 1.5 boolArray[i] = i%2 == 0 } b.Run("IntArray_Individual", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "int-array-individual", func() { for i := 0; i < b.N; i++ { L.PushValue(intArray) L.Pop(1) } }) }) b.Run("IntArray_Batch", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "int-array-batch", func() { for i := 0; i < b.N; i++ { L.BatchPushIntArray(intArray) L.Pop(1) } }) }) b.Run("StringArray_Individual", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "string-array-individual", func() { for i := 0; i < b.N; i++ { L.PushValue(stringArray) L.Pop(1) } }) }) b.Run("StringArray_Batch", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "string-array-batch", func() { for i := 0; i < b.N; i++ { L.BatchPushStringArray(stringArray) L.Pop(1) } }) }) b.Run("FloatArray_Individual", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "float-array-individual", func() { for i := 0; i < b.N; i++ { L.PushValue(floatArray) L.Pop(1) } }) }) b.Run("FloatArray_Batch", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "float-array-batch", func() { for i := 0; i < b.N; i++ { L.BatchPushFloatArray(floatArray) L.Pop(1) } }) }) b.Run("BoolArray_Individual", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "bool-array-individual", func() { for i := 0; i < b.N; i++ { L.PushValue(boolArray) L.Pop(1) } }) }) b.Run("BoolArray_Batch", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "bool-array-batch", func() { for i := 0; i < b.N; i++ { L.BatchPushBoolArray(boolArray) L.Pop(1) } }) }) } // BenchmarkBatchGlobalOperations compares global variable operations func BenchmarkBatchGlobalOperations(b *testing.B) { globals := map[string]string{ "APP_NAME": "myapp", "APP_VERSION": "1.0.0", "APP_ENV": "production", "DB_HOST": "localhost", "DB_PORT": "5432", "DB_NAME": "mydb", "CACHE_TTL": "300", "LOG_LEVEL": "info", } b.Run("Individual_Set", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "globals-individual-set", func() { for i := 0; i < b.N; i++ { for k, v := range globals { L.PushString(v) L.SetGlobal(k) } } }) }) b.Run("Batch_Set", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "globals-batch-set", func() { for i := 0; i < b.N; i++ { L.BatchSetGlobals(globals) } }) }) // Setup globals for read tests L := luajit.New() defer L.Close() L.BatchSetGlobals(globals) globalNames := make([]string, 0, len(globals)) for k := range globals { globalNames = append(globalNames, k) } b.Run("Individual_Get", func(b *testing.B) { TrackMemoryUsage(b, "globals-individual-get", func() { for i := 0; i < b.N; i++ { values := make(map[string]any) for _, name := range globalNames { L.GetGlobal(name) if val, err := L.ToValue(-1); err == nil { values[name] = val } L.Pop(1) } _ = values } }) }) b.Run("Batch_Get", func(b *testing.B) { TrackMemoryUsage(b, "globals-batch-get", func() { for i := 0; i < b.N; i++ { startTop := L.GetTop() L.BatchGetGlobals(globalNames) values := make(map[string]any, len(globalNames)) for j, name := range globalNames { if val, err := L.ToValue(startTop + j + 1); err == nil { values[name] = val } } L.SetTop(startTop) _ = values } }) }) } // BenchmarkBatchTableReading compares table field reading methods func BenchmarkBatchTableReading(b *testing.B) { L := luajit.New() defer L.Close() // Create a complex nested table structure setupCode := ` config = { database = { host = "localhost", port = 5432, name = "myapp", timeout = 30, ssl = true, pool = {min = 5, max = 20} }, server = { host = "0.0.0.0", port = 8080, workers = 4, timeout = 60, ssl = false, middleware = {"cors", "auth", "logging"} }, cache = { enabled = true, ttl = 300, size = 1000, backend = "redis", cluster = false }, logging = { level = "info", file = "/var/log/app.log", rotate = true, max_size = 100, format = "json" } } ` if err := L.DoString(setupCode); err != nil { b.Fatalf("Setup failed: %v", err) } topLevelKeys := []string{"database", "server", "cache", "logging"} dbKeys := []string{"host", "port", "name", "timeout", "ssl"} b.Run("Individual_TopLevel", func(b *testing.B) { TrackMemoryUsage(b, "table-read-individual", func() { for i := 0; i < b.N; i++ { L.GetGlobal("config") values := make(map[string]any) for _, key := range topLevelKeys { L.GetField(-1, key) if val, err := L.ToValue(-1); err == nil { values[key] = val } L.Pop(1) } L.Pop(1) _ = values } }) }) b.Run("Batch_TopLevel", func(b *testing.B) { TrackMemoryUsage(b, "table-read-batch", func() { for i := 0; i < b.N; i++ { L.GetGlobal("config") reader := L.NewBatchTableReader(-1) values, _ := reader.ReadFields(topLevelKeys) L.Pop(1) _ = values } }) }) b.Run("Individual_Nested", func(b *testing.B) { TrackMemoryUsage(b, "nested-read-individual", func() { for i := 0; i < b.N; i++ { L.GetGlobal("config") L.GetField(-1, "database") values := make(map[string]any) for _, key := range dbKeys { L.GetField(-1, key) if val, err := L.ToValue(-1); err == nil { values[key] = val } L.Pop(1) } L.Pop(2) _ = values } }) }) b.Run("Batch_Nested", func(b *testing.B) { TrackMemoryUsage(b, "nested-read-batch", func() { for i := 0; i < b.N; i++ { L.GetGlobal("config") L.GetField(-1, "database") reader := L.NewBatchTableReader(-1) values, _ := reader.ReadFields(dbKeys) L.Pop(2) _ = values } }) }) } // BenchmarkBatchValuePushing compares value pushing methods func BenchmarkBatchValuePushing(b *testing.B) { mixedValues := []any{ "string1", 42, true, 3.14, "string2", 100, false, 2.718, "string3", 256, true, 1.414, "string4", 500, false, 0.577, } stringValues := []any{"hello", "world", "test", "bench", "mark", "go", "lua", "jit"} numberValues := []any{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} b.Run("Mixed_Individual", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "mixed-individual", func() { for i := 0; i < b.N; i++ { for _, v := range mixedValues { L.PushValue(v) } L.Pop(len(mixedValues)) } }) }) b.Run("Mixed_Batch", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "mixed-batch", func() { for i := 0; i < b.N; i++ { pusher := L.NewBatchValuePusher() for _, v := range mixedValues { pusher.Add(v) } pusher.Push() L.Pop(len(mixedValues)) } }) }) b.Run("Strings_Individual", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "strings-individual", func() { for i := 0; i < b.N; i++ { for _, v := range stringValues { L.PushValue(v) } L.Pop(len(stringValues)) } }) }) b.Run("Strings_Batch", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "strings-batch", func() { for i := 0; i < b.N; i++ { pusher := L.NewBatchValuePusher() for _, v := range stringValues { pusher.Add(v) } pusher.Push() L.Pop(len(stringValues)) } }) }) b.Run("Numbers_Individual", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "numbers-individual", func() { for i := 0; i < b.N; i++ { for _, v := range numberValues { L.PushValue(v) } L.Pop(len(numberValues)) } }) }) b.Run("Numbers_Batch", func(b *testing.B) { L := luajit.New() defer L.Close() TrackMemoryUsage(b, "numbers-batch", func() { for i := 0; i < b.N; i++ { pusher := L.NewBatchValuePusher() for _, v := range numberValues { pusher.Add(v) } pusher.Push() L.Pop(len(numberValues)) } }) }) } // BenchmarkBatchExtraction compares array extraction methods func BenchmarkBatchExtraction(b *testing.B) { L := luajit.New() defer L.Close() // Create test arrays in Lua setupCode := ` int_array = {} for i = 1, 100 do int_array[i] = i * 2 end float_array = {} for i = 1, 100 do float_array[i] = i * 1.5 end string_array = {} for i = 1, 100 do string_array[i] = "item" .. i end bool_array = {} for i = 1, 100 do bool_array[i] = (i % 2 == 0) end ` if err := L.DoString(setupCode); err != nil { b.Fatalf("Setup failed: %v", err) } b.Run("IntArray_Individual", func(b *testing.B) { TrackMemoryUsage(b, "int-extract-individual", func() { for i := 0; i < b.N; i++ { L.GetGlobal("int_array") result := make([]int, 100) for j := 1; j <= 100; j++ { L.PushNumber(float64(j)) L.GetTable(-2) result[j-1] = int(L.ToNumber(-1)) L.Pop(1) } L.Pop(1) _ = result } }) }) b.Run("IntArray_Batch", func(b *testing.B) { TrackMemoryUsage(b, "int-extract-batch", func() { for i := 0; i < b.N; i++ { L.GetGlobal("int_array") result, _ := L.BatchExtractIntArray(-1, 100) L.Pop(1) _ = result } }) }) b.Run("FloatArray_Individual", func(b *testing.B) { TrackMemoryUsage(b, "float-extract-individual", func() { for i := 0; i < b.N; i++ { L.GetGlobal("float_array") result := make([]float64, 100) for j := 1; j <= 100; j++ { L.PushNumber(float64(j)) L.GetTable(-2) result[j-1] = L.ToNumber(-1) L.Pop(1) } L.Pop(1) _ = result } }) }) b.Run("FloatArray_Batch", func(b *testing.B) { TrackMemoryUsage(b, "float-extract-batch", func() { for i := 0; i < b.N; i++ { L.GetGlobal("float_array") result, _ := L.BatchExtractFloatArray(-1, 100) L.Pop(1) _ = result } }) }) b.Run("StringArray_Individual", func(b *testing.B) { TrackMemoryUsage(b, "string-extract-individual", func() { for i := 0; i < b.N; i++ { L.GetGlobal("string_array") result := make([]string, 100) for j := 1; j <= 100; j++ { L.PushNumber(float64(j)) L.GetTable(-2) result[j-1] = L.ToString(-1) L.Pop(1) } L.Pop(1) _ = result } }) }) b.Run("StringArray_Batch", func(b *testing.B) { TrackMemoryUsage(b, "string-extract-batch", func() { for i := 0; i < b.N; i++ { L.GetGlobal("string_array") result, _ := L.BatchExtractStringArray(-1, 100) L.Pop(1) _ = result } }) }) b.Run("BoolArray_Individual", func(b *testing.B) { TrackMemoryUsage(b, "bool-extract-individual", func() { for i := 0; i < b.N; i++ { L.GetGlobal("bool_array") result := make([]bool, 100) for j := 1; j <= 100; j++ { L.PushNumber(float64(j)) L.GetTable(-2) result[j-1] = L.ToBoolean(-1) L.Pop(1) } L.Pop(1) _ = result } }) }) b.Run("BoolArray_Batch", func(b *testing.B) { TrackMemoryUsage(b, "bool-extract-batch", func() { for i := 0; i < b.N; i++ { L.GetGlobal("bool_array") result, _ := L.BatchExtractBoolArray(-1, 100) L.Pop(1) _ = result } }) }) }