Compare commits
No commits in common. "6b9e2a0e201bdd34fba0972441434354aa5c67c5" and "a2b4b1c9272f849d9c1c913366f822e0be904ba2" have entirely different histories.
6b9e2a0e20
...
a2b4b1c927
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -21,5 +21,3 @@
|
|||
go.work
|
||||
|
||||
.idea
|
||||
|
||||
bench/profile_results
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
package luajit_bench
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Profiling flags
|
||||
var (
|
||||
cpuProfile = flag.String("cpuprofile", "", "write cpu profile to `file`")
|
||||
memProfile = flag.String("memprofile", "", "write memory profile to `file`")
|
||||
memProfileGC = flag.Bool("memprofilegc", false, "force GC before writing memory profile")
|
||||
blockProfile = flag.String("blockprofile", "", "write block profile to `file`")
|
||||
mutexProfile = flag.String("mutexprofile", "", "write mutex profile to `file`")
|
||||
)
|
||||
|
||||
// setupTestMain configures profiling for benchmarks
|
||||
func setupTestMain() {
|
||||
// Make sure the flags are parsed
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
// CPU profiling
|
||||
if *cpuProfile != "" {
|
||||
f, err := os.Create(*cpuProfile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create CPU profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to start CPU profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("CPU profiling enabled")
|
||||
}
|
||||
|
||||
// Block profiling (goroutine blocking)
|
||||
if *blockProfile != "" {
|
||||
runtime.SetBlockProfileRate(1)
|
||||
fmt.Println("Block profiling enabled")
|
||||
}
|
||||
|
||||
// Mutex profiling (lock contention)
|
||||
if *mutexProfile != "" {
|
||||
runtime.SetMutexProfileFraction(1)
|
||||
fmt.Println("Mutex profiling enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// teardownTestMain completes profiling and writes output files
|
||||
func teardownTestMain() {
|
||||
// Stop CPU profile
|
||||
if *cpuProfile != "" {
|
||||
pprof.StopCPUProfile()
|
||||
fmt.Println("CPU profile written to", *cpuProfile)
|
||||
}
|
||||
|
||||
// Write memory profile
|
||||
if *memProfile != "" {
|
||||
f, err := os.Create(*memProfile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create memory profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Force garbage collection before writing memory profile if requested
|
||||
if *memProfileGC {
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write memory profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Memory profile written to", *memProfile)
|
||||
}
|
||||
|
||||
// Write block profile
|
||||
if *blockProfile != "" {
|
||||
f, err := os.Create(*blockProfile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create block profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := pprof.Lookup("block").WriteTo(f, 0); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write block profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Block profile written to", *blockProfile)
|
||||
}
|
||||
|
||||
// Write mutex profile
|
||||
if *mutexProfile != "" {
|
||||
f, err := os.Create(*mutexProfile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create mutex profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := pprof.Lookup("mutex").WriteTo(f, 0); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write mutex profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Mutex profile written to", *mutexProfile)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMain is the entry point for all tests in this package
|
||||
func TestMain(m *testing.M) {
|
||||
setupTestMain()
|
||||
code := m.Run()
|
||||
teardownTestMain()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// MemStats captures a snapshot of memory statistics
|
||||
type MemStats struct {
|
||||
Alloc uint64
|
||||
TotalAlloc uint64
|
||||
Sys uint64
|
||||
Mallocs uint64
|
||||
Frees uint64
|
||||
HeapAlloc uint64
|
||||
}
|
||||
|
||||
// CaptureMemStats returns current memory statistics
|
||||
func CaptureMemStats() MemStats {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
return MemStats{
|
||||
Alloc: m.Alloc,
|
||||
TotalAlloc: m.TotalAlloc,
|
||||
Sys: m.Sys,
|
||||
Mallocs: m.Mallocs,
|
||||
Frees: m.Frees,
|
||||
HeapAlloc: m.HeapAlloc,
|
||||
}
|
||||
}
|
||||
|
||||
// TrackMemoryUsage runs fn and reports memory usage before and after
|
||||
func TrackMemoryUsage(b *testing.B, name string, fn func()) {
|
||||
b.Helper()
|
||||
|
||||
// Force GC before measurement
|
||||
runtime.GC()
|
||||
|
||||
// Capture memory stats before
|
||||
before := CaptureMemStats()
|
||||
|
||||
// Run the function
|
||||
fn()
|
||||
|
||||
// Force GC after measurement to get accurate stats
|
||||
runtime.GC()
|
||||
|
||||
// Capture memory stats after
|
||||
after := CaptureMemStats()
|
||||
|
||||
// Report stats
|
||||
b.ReportMetric(float64(after.Mallocs-before.Mallocs), name+"-mallocs")
|
||||
b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/float64(b.N), name+"-bytes/op")
|
||||
}
|
|
@ -16,14 +16,11 @@ func BenchmarkSimpleDoString(b *testing.B) {
|
|||
|
||||
code := "local x = 1 + 1"
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "dostring", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSimpleCompileAndRun benchmarks compile and run of a simple expression
|
||||
|
@ -36,14 +33,11 @@ func BenchmarkSimpleCompileAndRun(b *testing.B) {
|
|||
|
||||
code := "local x = 1 + 1"
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "compile-run", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.CompileAndRun(code, "simple"); err != nil {
|
||||
b.Fatalf("CompileAndRun failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.CompileAndRun(code, "simple"); err != nil {
|
||||
b.Fatalf("CompileAndRun failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSimpleCompileLoadRun benchmarks compile, load, and run of a simple expression
|
||||
|
@ -56,18 +50,15 @@ func BenchmarkSimpleCompileLoadRun(b *testing.B) {
|
|||
|
||||
code := "local x = 1 + 1"
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "compile-load-run", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
bytecode, err := state.CompileBytecode(code, "simple")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
bytecode, err := state.CompileBytecode(code, "simple")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
})
|
||||
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSimplePrecompiledBytecode benchmarks running precompiled bytecode
|
||||
|
@ -84,14 +75,11 @@ func BenchmarkSimplePrecompiledBytecode(b *testing.B) {
|
|||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkFunctionCallDoString benchmarks direct execution of a function call
|
||||
|
@ -114,14 +102,11 @@ func BenchmarkFunctionCallDoString(b *testing.B) {
|
|||
|
||||
code := "local result = add(10, 20)"
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "func-dostring", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkFunctionCallPrecompiled benchmarks precompiled function call
|
||||
|
@ -148,14 +133,11 @@ func BenchmarkFunctionCallPrecompiled(b *testing.B) {
|
|||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "func-precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "call"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "call"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoopDoString benchmarks direct execution of a loop
|
||||
|
@ -173,14 +155,11 @@ func BenchmarkLoopDoString(b *testing.B) {
|
|||
end
|
||||
`
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "loop-dostring", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoopPrecompiled benchmarks precompiled loop execution
|
||||
|
@ -202,14 +181,11 @@ func BenchmarkLoopPrecompiled(b *testing.B) {
|
|||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "loop-precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "loop"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "loop"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkTableOperationsDoString benchmarks direct execution of table operations
|
||||
|
@ -231,14 +207,11 @@ func BenchmarkTableOperationsDoString(b *testing.B) {
|
|||
end
|
||||
`
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "table-dostring", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkTableOperationsPrecompiled benchmarks precompiled table operations
|
||||
|
@ -264,14 +237,11 @@ func BenchmarkTableOperationsPrecompiled(b *testing.B) {
|
|||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "table-precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "table"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "table"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGoFunctionCall benchmarks calling a Go function from Lua
|
||||
|
@ -299,14 +269,11 @@ func BenchmarkGoFunctionCall(b *testing.B) {
|
|||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "go-func-call", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "gofunc"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "gofunc"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkComplexScript benchmarks a more complex script
|
||||
|
@ -355,14 +322,11 @@ func BenchmarkComplexScript(b *testing.B) {
|
|||
return result
|
||||
`
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "complex-script", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := state.ExecuteWithResult(code); err != nil {
|
||||
b.Fatalf("ExecuteWithResult failed: %v", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := state.ExecuteWithResult(code); err != nil {
|
||||
b.Fatalf("ExecuteWithResult failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkComplexScriptPrecompiled benchmarks a precompiled complex script
|
||||
|
@ -415,18 +379,15 @@ func BenchmarkComplexScriptPrecompiled(b *testing.B) {
|
|||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "complex-precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadBytecode(bytecode, "complex"); err != nil {
|
||||
b.Fatalf("LoadBytecode failed: %v", err)
|
||||
}
|
||||
if err := state.RunBytecodeWithResults(1); err != nil {
|
||||
b.Fatalf("RunBytecodeWithResults failed: %v", err)
|
||||
}
|
||||
state.Pop(1) // Pop the result
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadBytecode(bytecode, "complex"); err != nil {
|
||||
b.Fatalf("LoadBytecode failed: %v", err)
|
||||
}
|
||||
})
|
||||
if err := state.RunBytecodeWithResults(1); err != nil { // Assuming this method exists to get the return value
|
||||
b.Fatalf("RunBytecodeWithResults failed: %v", err)
|
||||
}
|
||||
state.Pop(1) // Pop the result
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMultipleExecutions benchmarks executing the same bytecode multiple times
|
||||
|
@ -457,16 +418,13 @@ func BenchmarkMultipleExecutions(b *testing.B) {
|
|||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "multiple-executions", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadBytecode(bytecode, "increment"); err != nil {
|
||||
b.Fatalf("LoadBytecode failed: %v", err)
|
||||
}
|
||||
if err := state.RunBytecodeWithResults(1); err != nil {
|
||||
b.Fatalf("RunBytecodeWithResults failed: %v", err)
|
||||
}
|
||||
state.Pop(1) // Pop the result
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadBytecode(bytecode, "increment"); err != nil {
|
||||
b.Fatalf("LoadBytecode failed: %v", err)
|
||||
}
|
||||
})
|
||||
if err := state.RunBytecodeWithResults(1); err != nil { // Assuming this method exists
|
||||
b.Fatalf("RunBytecodeWithResults failed: %v", err)
|
||||
}
|
||||
state.Pop(1) // Pop the result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,17 +72,14 @@ func BenchmarkLuaDirectExecution(b *testing.B) {
|
|||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -126,13 +123,11 @@ func BenchmarkLuaBytecodeExecution(b *testing.B) {
|
|||
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)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := L.LoadAndRunBytecode(bytecode, bc.name); err != nil {
|
||||
b.Fatalf("Error executing bytecode: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Easy script to run benchmarks with profiling enabled
|
||||
# Usage: ./profile_benchmarks.sh [benchmark_pattern]
|
||||
|
||||
set -e
|
||||
|
||||
# Default values
|
||||
BENCHMARK=${1:-"."}
|
||||
OUTPUT_DIR="./profile_results"
|
||||
CPU_PROFILE="$OUTPUT_DIR/cpu.prof"
|
||||
MEM_PROFILE="$OUTPUT_DIR/mem.prof"
|
||||
BLOCK_PROFILE="$OUTPUT_DIR/block.prof"
|
||||
MUTEX_PROFILE="$OUTPUT_DIR/mutex.prof"
|
||||
TRACE_FILE="$OUTPUT_DIR/trace.out"
|
||||
HTML_OUTPUT="$OUTPUT_DIR/profile_report.html"
|
||||
|
||||
# Create output directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
echo "Running benchmarks with profiling enabled..."
|
||||
|
||||
# Run benchmarks with profiling flags
|
||||
go test -bench="$BENCHMARK" -benchmem -cpuprofile="$CPU_PROFILE" -memprofile="$MEM_PROFILE" -blockprofile="$BLOCK_PROFILE" -mutexprofile="$MUTEX_PROFILE" -count=5 -timeout=30m
|
||||
|
||||
echo "Generating CPU profile analysis..."
|
||||
go tool pprof -http=":1880" -output="$OUTPUT_DIR/cpu_graph.svg" "$CPU_PROFILE"
|
||||
|
||||
echo "Generating memory profile analysis..."
|
||||
go tool pprof -http=":1880" -output="$OUTPUT_DIR/mem_graph.svg" "$MEM_PROFILE"
|
||||
|
||||
# Generate a simple HTML report
|
||||
cat > "$HTML_OUTPUT" << EOF
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>LuaJIT Benchmark Profiling Results</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
h1, h2 { color: #333; }
|
||||
.profile { margin-bottom: 30px; }
|
||||
img { max-width: 100%; border: 1px solid #ddd; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>LuaJIT Benchmark Profiling Results</h1>
|
||||
<p>Generated on: $(date)</p>
|
||||
|
||||
<div class="profile">
|
||||
<h2>CPU Profile</h2>
|
||||
<img src="cpu_graph.svg" alt="CPU Profile Graph">
|
||||
<p>Command to explore: <code>go tool pprof $CPU_PROFILE</code></p>
|
||||
</div>
|
||||
|
||||
<div class="profile">
|
||||
<h2>Memory Profile</h2>
|
||||
<img src="mem_graph.svg" alt="Memory Profile Graph">
|
||||
<p>Command to explore: <code>go tool pprof $MEM_PROFILE</code></p>
|
||||
</div>
|
||||
|
||||
<div class="profile">
|
||||
<h2>Tips for Profile Analysis</h2>
|
||||
<ul>
|
||||
<li>Use <code>go tool pprof -http=:8080 $CPU_PROFILE</code> for interactive web UI</li>
|
||||
<li>Use <code>top10</code> in pprof to see the top 10 functions by CPU/memory usage</li>
|
||||
<li>Use <code>list FunctionName</code> to see line-by-line stats for a specific function</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
echo "Profiling complete! Results available in $OUTPUT_DIR"
|
||||
echo "View the HTML report at $HTML_OUTPUT"
|
||||
echo ""
|
||||
echo "For detailed interactive analysis, run:"
|
||||
echo " go tool pprof -http=:1880 $CPU_PROFILE # For CPU profile"
|
||||
echo " go tool pprof -http=:1880 $MEM_PROFILE # For memory profile"
|
111
bytecode.go
111
bytecode.go
|
@ -38,16 +38,6 @@ int direct_bytecode_writer(lua_State *L, const void *p, size_t sz, void *ud) {
|
|||
data[1] = (void*)(current_size + sz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Combined load and run bytecode in a single call
|
||||
int load_and_run_bytecode(lua_State *L, const unsigned char *buf, size_t len,
|
||||
const char *name, int nresults) {
|
||||
BytecodeReader reader = {buf, len, name};
|
||||
int status = lua_load(L, bytecode_reader, &reader, name);
|
||||
if (status != 0) return status;
|
||||
|
||||
return lua_pcall(L, 0, nresults, 0);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
@ -65,10 +55,9 @@ func (s *State) CompileBytecode(code string, name string) ([]byte, error) {
|
|||
data := [2]unsafe.Pointer{nil, nil}
|
||||
|
||||
// Dump the function to bytecode
|
||||
status := C.lua_dump(s.L, (*[0]byte)(unsafe.Pointer(C.direct_bytecode_writer)), unsafe.Pointer(&data))
|
||||
if status != 0 {
|
||||
return nil, fmt.Errorf("failed to dump bytecode: status %d", status)
|
||||
}
|
||||
err := s.safeCall(func() C.int {
|
||||
return C.lua_dump(s.L, (*[0]byte)(unsafe.Pointer(C.direct_bytecode_writer)), unsafe.Pointer(&data))
|
||||
})
|
||||
|
||||
// Get result
|
||||
var bytecode []byte
|
||||
|
@ -81,6 +70,10 @@ func (s *State) CompileBytecode(code string, name string) ([]byte, error) {
|
|||
|
||||
s.Pop(1) // Remove the function from stack
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dump bytecode: %w", err)
|
||||
}
|
||||
|
||||
return bytecode, nil
|
||||
}
|
||||
|
||||
|
@ -94,20 +87,17 @@ func (s *State) LoadBytecode(bytecode []byte, name string) error {
|
|||
defer C.free(unsafe.Pointer(cname))
|
||||
|
||||
// Load the bytecode
|
||||
status := C.load_bytecode(
|
||||
s.L,
|
||||
(*C.uchar)(unsafe.Pointer(&bytecode[0])),
|
||||
C.size_t(len(bytecode)),
|
||||
cname,
|
||||
)
|
||||
err := s.safeCall(func() C.int {
|
||||
return C.load_bytecode(
|
||||
s.L,
|
||||
(*C.uchar)(unsafe.Pointer(&bytecode[0])),
|
||||
C.size_t(len(bytecode)),
|
||||
cname,
|
||||
)
|
||||
})
|
||||
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load bytecode: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -121,76 +111,25 @@ func (s *State) RunBytecode() error {
|
|||
// RunBytecodeWithResults executes bytecode and keeps nresults on the stack
|
||||
// Use LUA_MULTRET (-1) to keep all results
|
||||
func (s *State) RunBytecodeWithResults(nresults int) error {
|
||||
status := C.lua_pcall(s.L, 0, C.int(nresults), 0)
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return s.safeCall(func() C.int {
|
||||
return C.lua_pcall(s.L, 0, C.int(nresults), 0)
|
||||
})
|
||||
}
|
||||
|
||||
// LoadAndRunBytecode loads and executes bytecode in a single CGO transition
|
||||
// LoadAndRunBytecode loads and executes bytecode
|
||||
func (s *State) LoadAndRunBytecode(bytecode []byte, name string) error {
|
||||
if len(bytecode) == 0 {
|
||||
return fmt.Errorf("empty bytecode")
|
||||
}
|
||||
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
|
||||
// Use combined load and run function
|
||||
status := C.load_and_run_bytecode(
|
||||
s.L,
|
||||
(*C.uchar)(unsafe.Pointer(&bytecode[0])),
|
||||
C.size_t(len(bytecode)),
|
||||
cname,
|
||||
0, // No results
|
||||
)
|
||||
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
if err := s.LoadBytecode(bytecode, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.RunBytecode()
|
||||
}
|
||||
|
||||
// LoadAndRunBytecodeWithResults loads and executes bytecode, preserving results
|
||||
func (s *State) LoadAndRunBytecodeWithResults(bytecode []byte, name string, nresults int) error {
|
||||
if len(bytecode) == 0 {
|
||||
return fmt.Errorf("empty bytecode")
|
||||
}
|
||||
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
|
||||
// Use combined load and run function
|
||||
status := C.load_and_run_bytecode(
|
||||
s.L,
|
||||
(*C.uchar)(unsafe.Pointer(&bytecode[0])),
|
||||
C.size_t(len(bytecode)),
|
||||
cname,
|
||||
C.int(nresults),
|
||||
)
|
||||
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
if err := s.LoadBytecode(bytecode, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.RunBytecodeWithResults(nresults)
|
||||
}
|
||||
|
||||
// CompileAndRun compiles and immediately executes Lua code
|
||||
|
|
149
wrapper.go
149
wrapper.go
|
@ -11,7 +11,13 @@ package luajit
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Direct execution helpers to minimize CGO transitions
|
||||
// Optimized helpers for common operations
|
||||
static int get_abs_index(lua_State *L, int idx) {
|
||||
if (idx > 0 || idx <= LUA_REGISTRYINDEX) return idx;
|
||||
return lua_gettop(L) + idx + 1;
|
||||
}
|
||||
|
||||
// Combined load and execute with no results
|
||||
static int do_string(lua_State *L, const char *s) {
|
||||
int status = luaL_loadstring(L, s);
|
||||
if (status == 0) {
|
||||
|
@ -20,6 +26,7 @@ static int do_string(lua_State *L, const char *s) {
|
|||
return status;
|
||||
}
|
||||
|
||||
// Combined load and execute file
|
||||
static int do_file(lua_State *L, const char *filename) {
|
||||
int status = luaL_loadfile(L, filename);
|
||||
if (status == 0) {
|
||||
|
@ -27,12 +34,6 @@ static int do_file(lua_State *L, const char *filename) {
|
|||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static int execute_with_results(lua_State *L, const char *code, int store_results) {
|
||||
int status = luaL_loadstring(L, code);
|
||||
if (status != 0) return status;
|
||||
return lua_pcall(L, 0, store_results ? LUA_MULTRET : 0, 0);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
@ -338,16 +339,9 @@ func (s *State) LoadString(code string) error {
|
|||
ccode := C.CString(code)
|
||||
defer C.free(unsafe.Pointer(ccode))
|
||||
|
||||
status := C.luaL_loadstring(s.L, ccode)
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return s.safeCall(func() C.int {
|
||||
return C.luaL_loadstring(s.L, ccode)
|
||||
})
|
||||
}
|
||||
|
||||
// LoadFile loads a Lua chunk from a file without executing it
|
||||
|
@ -355,30 +349,16 @@ func (s *State) LoadFile(filename string) error {
|
|||
cfilename := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(cfilename))
|
||||
|
||||
status := C.luaL_loadfile(s.L, cfilename)
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return s.safeCall(func() C.int {
|
||||
return C.luaL_loadfile(s.L, cfilename)
|
||||
})
|
||||
}
|
||||
|
||||
// Call calls a function with the given number of arguments and results
|
||||
func (s *State) Call(nargs, nresults int) error {
|
||||
status := C.lua_pcall(s.L, C.int(nargs), C.int(nresults), 0)
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return s.safeCall(func() C.int {
|
||||
return C.lua_pcall(s.L, C.int(nargs), C.int(nresults), 0)
|
||||
})
|
||||
}
|
||||
|
||||
// DoString executes a Lua string and cleans up the stack
|
||||
|
@ -386,16 +366,9 @@ func (s *State) DoString(code string) error {
|
|||
ccode := C.CString(code)
|
||||
defer C.free(unsafe.Pointer(ccode))
|
||||
|
||||
status := C.do_string(s.L, ccode)
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return s.safeCall(func() C.int {
|
||||
return C.do_string(s.L, ccode)
|
||||
})
|
||||
}
|
||||
|
||||
// DoFile executes a Lua file and cleans up the stack
|
||||
|
@ -403,16 +376,9 @@ func (s *State) DoFile(filename string) error {
|
|||
cfilename := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(cfilename))
|
||||
|
||||
status := C.do_file(s.L, cfilename)
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return s.safeCall(func() C.int {
|
||||
return C.do_file(s.L, cfilename)
|
||||
})
|
||||
}
|
||||
|
||||
// Execute executes a Lua string and returns the number of results left on the stack
|
||||
|
@ -422,17 +388,25 @@ func (s *State) Execute(code string) (int, error) {
|
|||
ccode := C.CString(code)
|
||||
defer C.free(unsafe.Pointer(ccode))
|
||||
|
||||
status := C.execute_with_results(s.L, ccode, 1) // store_results=true
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
var nresults int
|
||||
err := s.safeCall(func() C.int {
|
||||
status := C.luaL_loadstring(s.L, ccode)
|
||||
if status != 0 {
|
||||
return status
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
|
||||
status = C.lua_pcall(s.L, 0, C.LUA_MULTRET, 0)
|
||||
if status == 0 {
|
||||
nresults = s.GetTop() - baseTop
|
||||
}
|
||||
return status
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return s.GetTop() - baseTop, nil
|
||||
return nresults, nil
|
||||
}
|
||||
|
||||
// ExecuteWithResult executes a Lua string and returns the first result
|
||||
|
@ -452,20 +426,6 @@ func (s *State) ExecuteWithResult(code string) (any, error) {
|
|||
return s.ToValue(-nresults)
|
||||
}
|
||||
|
||||
// BatchExecute executes multiple statements with a single CGO transition
|
||||
func (s *State) BatchExecute(statements []string) error {
|
||||
// Join statements with semicolons
|
||||
combinedCode := ""
|
||||
for i, stmt := range statements {
|
||||
combinedCode += stmt
|
||||
if i < len(statements)-1 {
|
||||
combinedCode += "; "
|
||||
}
|
||||
}
|
||||
|
||||
return s.DoString(combinedCode)
|
||||
}
|
||||
|
||||
// Package path operations
|
||||
|
||||
// SetPackagePath sets the Lua package.path
|
||||
|
@ -481,3 +441,36 @@ func (s *State) AddPackagePath(path string) error {
|
|||
code := fmt.Sprintf(`package.path = package.path .. ";%s"`, path)
|
||||
return s.DoString(code)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// checkStack ensures there is enough space on the Lua stack
|
||||
func (s *State) checkStack(n int) error {
|
||||
if C.lua_checkstack(s.L, C.int(n)) == 0 {
|
||||
return fmt.Errorf("stack overflow (cannot allocate %d slots)", n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// safeCall wraps a potentially dangerous C call with stack checking
|
||||
func (s *State) safeCall(f func() C.int) error {
|
||||
// Ensure we have enough stack space
|
||||
if err := s.checkStack(LUA_MINSTACK); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make the call
|
||||
status := f()
|
||||
|
||||
// Check for errors
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user