optimize table

This commit is contained in:
Sky Johnson 2025-03-27 21:42:58 -05:00
parent fed0c2ad34
commit 29679349ef

121
table.go
View File

@ -6,7 +6,8 @@ package luajit
#include <lauxlib.h> #include <lauxlib.h>
#include <stdlib.h> #include <stdlib.h>
static size_t get_table_length(lua_State *L, int index) { // Simple direct length check
size_t get_table_length(lua_State *L, int index) {
return lua_objlen(L, index); return lua_objlen(L, index);
} }
*/ */
@ -14,70 +15,53 @@ import "C"
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"sync"
) )
// Use a pool to reduce GC pressure when handling many tables
var tablePool = sync.Pool{
New: func() any {
return make(map[string]any)
},
}
// GetTableLength returns the length of a table at the given index // GetTableLength returns the length of a table at the given index
func (s *State) GetTableLength(index int) int { func (s *State) GetTableLength(index int) int {
return int(C.get_table_length(s.L, C.int(index))) return int(C.get_table_length(s.L, C.int(index)))
} }
// getTableFromPool gets a map from the pool and ensures it's empty
func getTableFromPool() map[string]any {
table := tablePool.Get().(map[string]any)
// Clear any existing entries
for k := range table {
delete(table, k)
}
return table
}
// putTableToPool returns a map to the pool
func putTableToPool(table map[string]any) {
tablePool.Put(table)
}
// PushTable pushes a Go map onto the Lua stack as a table // PushTable pushes a Go map onto the Lua stack as a table
func (s *State) PushTable(table map[string]any) error { func (s *State) PushTable(table map[string]any) error {
// Create table with appropriate capacity hints // Fast path for array tables
s.CreateTable(0, len(table)) if arr, ok := table[""]; ok {
if floatArr, ok := arr.([]float64); ok {
// Add each key-value pair s.CreateTable(len(floatArr), 0)
for k, v := range table { for i, v := range floatArr {
// Push key s.PushNumber(float64(i + 1))
s.PushString(k) s.PushNumber(v)
s.SetTable(-3)
// Push value }
return nil
} else if anyArr, ok := arr.([]any); ok {
s.CreateTable(len(anyArr), 0)
for i, v := range anyArr {
s.PushNumber(float64(i + 1))
if err := s.PushValue(v); err != nil { if err := s.PushValue(v); err != nil {
return err return err
} }
s.SetTable(-3)
}
return nil
}
}
// t[k] = v // Regular table case - optimize capacity hint
s.CreateTable(0, len(table))
// Add each key-value pair directly
for k, v := range table {
s.PushString(k)
if err := s.PushValue(v); err != nil {
return err
}
s.SetTable(-3) s.SetTable(-3)
} }
// Return pooled tables to the pool
if isPooledTable(table) {
putTableToPool(table)
}
return nil return nil
} }
// isPooledTable detects if a table came from our pool
func isPooledTable(table map[string]any) bool {
// Check for our special marker - used for array tables in the pool
_, hasEmptyKey := table[""]
return len(table) == 1 && hasEmptyKey
}
// ToTable converts a Lua table at the given index to a Go map // ToTable converts a Lua table at the given index to a Go map
func (s *State) ToTable(index int) (map[string]any, error) { func (s *State) ToTable(index int) (map[string]any, error) {
absIdx := s.absIndex(index) absIdx := s.absIndex(index)
@ -88,34 +72,41 @@ func (s *State) ToTable(index int) (map[string]any, error) {
// Try to detect array-like tables first // Try to detect array-like tables first
length := s.GetTableLength(absIdx) length := s.GetTableLength(absIdx)
if length > 0 { if length > 0 {
// Check if this is an array-like table // Fast path for common array case
isArray := true allNumbers := true
array := make([]float64, length)
for i := 1; i <= length; i++ { // Sample first few values to check if it's likely an array of numbers
for i := 1; i <= min(length, 5); i++ {
s.PushNumber(float64(i)) s.PushNumber(float64(i))
s.GetTable(absIdx) s.GetTable(absIdx)
if !s.IsNumber(-1) { if !s.IsNumber(-1) {
isArray = false allNumbers = false
s.Pop(1) s.Pop(1)
break break
} }
s.Pop(1)
}
if allNumbers {
// Efficiently extract array values
array := make([]float64, length)
for i := 1; i <= length; i++ {
s.PushNumber(float64(i))
s.GetTable(absIdx)
array[i-1] = s.ToNumber(-1) array[i-1] = s.ToNumber(-1)
s.Pop(1) s.Pop(1)
} }
if isArray { // Return array as a special table with empty key
// Return array as a special pooled table with empty key result := make(map[string]any, 1)
result := getTableFromPool()
result[""] = array result[""] = array
return result, nil return result, nil
} }
} }
// Handle regular table // Handle regular table with pre-allocated capacity
table := getTableFromPool() table := make(map[string]any, max(length, 8))
// Iterate through all key-value pairs // Iterate through all key-value pairs
s.PushNil() // Start iteration with nil key s.PushNil() // Start iteration with nil key
@ -140,11 +131,10 @@ func (s *State) ToTable(index int) (map[string]any, error) {
value, err := s.ToValue(-1) value, err := s.ToValue(-1)
if err != nil { if err != nil {
s.Pop(2) // Pop both key and value s.Pop(2) // Pop both key and value
putTableToPool(table) // Return the table to the pool on error
return nil, err return nil, err
} }
// Handle nested array tables // Unwrap nested array tables
if m, ok := value.(map[string]any); ok { if m, ok := value.(map[string]any); ok {
if arr, ok := m[""]; ok { if arr, ok := m[""]; ok {
value = arr value = arr
@ -157,3 +147,18 @@ func (s *State) ToTable(index int) (map[string]any, error) {
return table, nil return table, nil
} }
// Helper functions for min/max operations
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}