From 081fb0b7de4c6296f701eb4cd48ceb6a8aadeaf8 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Wed, 2 Jul 2025 11:59:39 -0500 Subject: [PATCH] more attempts at batching --- batch.go | 828 ++++++++++++------------------------------ bench/bench_test.go | 313 ++++++++++++++++ bench/ezbench_test.go | 634 ++++++++++++++++++++++++++++++++ bench/snapshot.txt | 27 ++ 4 files changed, 1199 insertions(+), 603 deletions(-) create mode 100644 bench/snapshot.txt diff --git a/batch.go b/batch.go index b1b98d9..6114425 100644 --- a/batch.go +++ b/batch.go @@ -6,247 +6,84 @@ package luajit #include #include -// Batch table field setting - single C call for multiple fields -int batch_set_string_fields(lua_State *L, int table_idx, - char **keys, char **values, int count) { +// Fast array creation using lua_rawseti +void batch_push_int_array_fast(lua_State *L, int *values, int count) { + lua_createtable(L, count, 0); + for (int i = 0; i < count; i++) { + lua_pushnumber(L, values[i]); + lua_rawseti(L, -2, i + 1); + } +} + +void batch_push_float_array_fast(lua_State *L, double *values, int count) { + lua_createtable(L, count, 0); + for (int i = 0; i < count; i++) { + lua_pushnumber(L, values[i]); + lua_rawseti(L, -2, i + 1); + } +} + +void batch_push_string_array_fast(lua_State *L, char **values, int count) { + lua_createtable(L, count, 0); + for (int i = 0; i < count; i++) { + lua_pushstring(L, values[i]); + lua_rawseti(L, -2, i + 1); + } +} + +// Fast field setting +void batch_set_string_fields_fast(lua_State *L, int table_idx, + char **keys, char **values, int count) { for (int i = 0; i < count; i++) { lua_pushstring(L, values[i]); lua_setfield(L, table_idx, keys[i]); } - return 0; } -int batch_set_number_fields(lua_State *L, int table_idx, - char **keys, double *values, int count) { +void batch_set_number_fields_fast(lua_State *L, int table_idx, + char **keys, double *values, int count) { for (int i = 0; i < count; i++) { lua_pushnumber(L, values[i]); lua_setfield(L, table_idx, keys[i]); } - return 0; -} - -int batch_set_bool_fields(lua_State *L, int table_idx, - char **keys, int *values, int count) { - for (int i = 0; i < count; i++) { - lua_pushboolean(L, values[i]); - lua_setfield(L, table_idx, keys[i]); - } - return 0; -} - -// Batch array creation - build entire array in C -int batch_push_int_array(lua_State *L, int *values, int count) { - lua_createtable(L, count, 0); - for (int i = 0; i < count; i++) { - lua_pushnumber(L, i + 1); - lua_pushnumber(L, values[i]); - lua_settable(L, -3); - } - return 0; -} - -int batch_push_float_array(lua_State *L, double *values, int count) { - lua_createtable(L, count, 0); - for (int i = 0; i < count; i++) { - lua_pushnumber(L, i + 1); - lua_pushnumber(L, values[i]); - lua_settable(L, -3); - } - return 0; -} - -int batch_push_string_array(lua_State *L, char **values, int count) { - lua_createtable(L, count, 0); - for (int i = 0; i < count; i++) { - lua_pushnumber(L, i + 1); - lua_pushstring(L, values[i]); - lua_settable(L, -3); - } - return 0; -} - -int batch_push_bool_array(lua_State *L, int *values, int count) { - lua_createtable(L, count, 0); - for (int i = 0; i < count; i++) { - lua_pushnumber(L, i + 1); - lua_pushboolean(L, values[i]); - lua_settable(L, -3); - } - return 0; -} - -// Batch array extraction - extract entire array in C -int batch_extract_int_array(lua_State *L, int table_idx, int *buffer, int length) { - for (int i = 1; i <= length; i++) { - lua_pushnumber(L, i); - lua_gettable(L, table_idx); - buffer[i-1] = (int)lua_tonumber(L, -1); - lua_pop(L, 1); - } - return 0; -} - -int batch_extract_float_array(lua_State *L, int table_idx, double *buffer, int length) { - for (int i = 1; i <= length; i++) { - lua_pushnumber(L, i); - lua_gettable(L, table_idx); - buffer[i-1] = lua_tonumber(L, -1); - lua_pop(L, 1); - } - return 0; -} - -int batch_extract_string_array(lua_State *L, int table_idx, char **buffer, int length) { - for (int i = 1; i <= length; i++) { - lua_pushnumber(L, i); - lua_gettable(L, table_idx); - size_t len; - const char *str = lua_tolstring(L, -1, &len); - if (str) { - buffer[i-1] = malloc(len + 1); - memcpy(buffer[i-1], str, len); - buffer[i-1][len] = '\0'; - } else { - buffer[i-1] = malloc(1); - buffer[i-1][0] = '\0'; - } - lua_pop(L, 1); - } - return 0; -} - -int batch_extract_bool_array(lua_State *L, int table_idx, int *buffer, int length) { - for (int i = 1; i <= length; i++) { - lua_pushnumber(L, i); - lua_gettable(L, table_idx); - buffer[i-1] = lua_toboolean(L, -1); - lua_pop(L, 1); - } - return 0; -} - -// Batch global variable operations -int batch_set_globals(lua_State *L, char **names, char **values, int count) { - for (int i = 0; i < count; i++) { - lua_pushstring(L, values[i]); - lua_setfield(L, LUA_GLOBALSINDEX, names[i]); - } - return 0; -} - -int batch_get_globals(lua_State *L, char **names, int count) { - for (int i = 0; i < count; i++) { - lua_getfield(L, LUA_GLOBALSINDEX, names[i]); - } - return 0; -} - -// Batch table field reading -int batch_get_table_fields(lua_State *L, int table_idx, char **keys, int count) { - for (int i = 0; i < count; i++) { - lua_getfield(L, table_idx, keys[i]); - } - return 0; -} - -// Batch type checking -int batch_check_types(lua_State *L, int *indices, int *expected_types, int count) { - for (int i = 0; i < count; i++) { - int actual = lua_type(L, indices[i]); - if (actual != expected_types[i]) { - return i + 1; // Return 1-based index of first mismatch - } - } - return 0; // All types match -} - -// Batch stack value pushes -int batch_push_mixed_values(lua_State *L, void **values, int *types, int count) { - for (int i = 0; i < count; i++) { - switch (types[i]) { - case LUA_TNIL: - lua_pushnil(L); - break; - case LUA_TBOOLEAN: - lua_pushboolean(L, *(int*)values[i]); - break; - case LUA_TNUMBER: - lua_pushnumber(L, *(double*)values[i]); - break; - case LUA_TSTRING: - lua_pushstring(L, (char*)values[i]); - break; - } - } - return 0; -} - -// Batch function registration -int batch_register_functions(lua_State *L, char **names, lua_CFunction *funcs, int count) { - for (int i = 0; i < count; i++) { - lua_pushcfunction(L, funcs[i]); - lua_setglobal(L, names[i]); - } - return 0; -} - -// Batch package path operations -int batch_add_package_paths(lua_State *L, char **paths, int count) { - lua_getglobal(L, "package"); - lua_getfield(L, -1, "path"); - - const char *current = lua_tostring(L, -1); - size_t total_len = strlen(current) + 1; - - for (int i = 0; i < count; i++) { - total_len += strlen(paths[i]) + 1; // +1 for semicolon - } - - char *new_path = malloc(total_len + 1); - strcpy(new_path, current); - - for (int i = 0; i < count; i++) { - strcat(new_path, ";"); - strcat(new_path, paths[i]); - } - - lua_pushstring(L, new_path); - lua_setfield(L, -3, "path"); - lua_pop(L, 2); // Remove package table and old path - - free(new_path); - return 0; -} - -// Batch error context building -void batch_build_error_context(lua_State *L, char **context_parts, int count, char *output, size_t max_len) { - size_t pos = 0; - for (int i = 0; i < count && pos < max_len - 1; i++) { - size_t part_len = strlen(context_parts[i]); - if (pos + part_len + 1 < max_len) { - if (i > 0) output[pos++] = ' '; - memcpy(output + pos, context_parts[i], part_len); - pos += part_len; - } - } - output[pos] = '\0'; } */ import "C" import ( "fmt" - "strings" + "sync" "unsafe" ) -// BatchSetStringFields sets multiple string fields on a table in one C call +// Buffer pools for reuse +var ( + intBufferPool = sync.Pool{ + New: func() any { return make([]C.int, 0, 128) }, + } + floatBufferPool = sync.Pool{ + New: func() any { return make([]C.double, 0, 128) }, + } + charPtrPool = sync.Pool{ + New: func() any { return make([]*C.char, 0, 32) }, + } +) + func (s *State) BatchSetStringFields(tableIndex int, fields map[string]string) error { - if len(fields) == 0 { + count := len(fields) + if count == 0 { return nil } - keys := make([]*C.char, len(fields)) - values := make([]*C.char, len(fields)) + if count < 8 { + for k, v := range fields { + s.PushString(v) + s.SetField(tableIndex, k) + } + return nil + } + + keys := make([]*C.char, count) + values := make([]*C.char, count) i := 0 for k, v := range fields { @@ -255,9 +92,9 @@ func (s *State) BatchSetStringFields(tableIndex int, fields map[string]string) e i++ } - C.batch_set_string_fields(s.L, C.int(tableIndex), &keys[0], &values[0], C.int(len(fields))) + C.batch_set_string_fields_fast(s.L, C.int(tableIndex), &keys[0], &values[0], C.int(count)) - for j := range len(fields) { + for j := 0; j < count; j++ { C.free(unsafe.Pointer(keys[j])) C.free(unsafe.Pointer(values[j])) } @@ -265,14 +102,22 @@ func (s *State) BatchSetStringFields(tableIndex int, fields map[string]string) e return nil } -// BatchSetNumberFields sets multiple number fields on a table in one C call func (s *State) BatchSetNumberFields(tableIndex int, fields map[string]float64) error { - if len(fields) == 0 { + count := len(fields) + if count == 0 { return nil } - keys := make([]*C.char, len(fields)) - values := make([]C.double, len(fields)) + if count < 8 { + for k, v := range fields { + s.PushNumber(v) + s.SetField(tableIndex, k) + } + return nil + } + + keys := make([]*C.char, count) + values := make([]C.double, count) i := 0 for k, v := range fields { @@ -281,89 +126,109 @@ func (s *State) BatchSetNumberFields(tableIndex int, fields map[string]float64) i++ } - C.batch_set_number_fields(s.L, C.int(tableIndex), &keys[0], &values[0], C.int(len(fields))) + C.batch_set_number_fields_fast(s.L, C.int(tableIndex), &keys[0], &values[0], C.int(count)) - for j := range len(fields) { + for j := 0; j < count; j++ { C.free(unsafe.Pointer(keys[j])) } return nil } -// BatchSetBoolFields sets multiple boolean fields on a table in one C call func (s *State) BatchSetBoolFields(tableIndex int, fields map[string]bool) error { - if len(fields) == 0 { - return nil - } - - keys := make([]*C.char, len(fields)) - values := make([]C.int, len(fields)) - - i := 0 for k, v := range fields { - keys[i] = C.CString(k) - if v { - values[i] = 1 - } else { - values[i] = 0 - } - i++ + s.PushBoolean(v) + s.SetField(tableIndex, k) } - - C.batch_set_bool_fields(s.L, C.int(tableIndex), &keys[0], &values[0], C.int(len(fields))) - - for j := range len(fields) { - C.free(unsafe.Pointer(keys[j])) - } - return nil } -// BatchPushIntArray creates and pushes an int array in a single C call func (s *State) BatchPushIntArray(values []int) error { if len(values) == 0 { s.CreateTable(0, 0) return nil } - cValues := make([]C.int, len(values)) - for i, v := range values { - cValues[i] = C.int(v) + if len(values) < 10 { + s.CreateTable(len(values), 0) + for i, v := range values { + s.PushNumber(float64(v)) + C.lua_rawseti(s.L, -2, C.int(i+1)) + } + return nil } - C.batch_push_int_array(s.L, &cValues[0], C.int(len(values))) + // Use pooled buffer + intBuf := intBufferPool.Get().([]C.int) + defer intBufferPool.Put(intBuf[:0]) + + if cap(intBuf) < len(values) { + intBuf = make([]C.int, len(values)) + } else { + intBuf = intBuf[:len(values)] + } + + for i, v := range values { + intBuf[i] = C.int(v) + } + + C.batch_push_int_array_fast(s.L, &intBuf[0], C.int(len(values))) return nil } -// BatchPushFloatArray creates and pushes a float array in a single C call func (s *State) BatchPushFloatArray(values []float64) error { if len(values) == 0 { s.CreateTable(0, 0) return nil } - cValues := make([]C.double, len(values)) - for i, v := range values { - cValues[i] = C.double(v) + if len(values) < 10 { + s.CreateTable(len(values), 0) + for i, v := range values { + s.PushNumber(v) + C.lua_rawseti(s.L, -2, C.int(i+1)) + } + return nil } - C.batch_push_float_array(s.L, &cValues[0], C.int(len(values))) + floatBuf := floatBufferPool.Get().([]C.double) + defer floatBufferPool.Put(floatBuf[:0]) + + if cap(floatBuf) < len(values) { + floatBuf = make([]C.double, len(values)) + } else { + floatBuf = floatBuf[:len(values)] + } + + for i, v := range values { + floatBuf[i] = C.double(v) + } + + C.batch_push_float_array_fast(s.L, &floatBuf[0], C.int(len(values))) return nil } -// BatchPushStringArray creates and pushes a string array in a single C call func (s *State) BatchPushStringArray(values []string) error { if len(values) == 0 { s.CreateTable(0, 0) return nil } + if len(values) < 6 { + s.CreateTable(len(values), 0) + for i, v := range values { + s.PushString(v) + C.lua_rawseti(s.L, -2, C.int(i+1)) + } + return nil + } + cValues := make([]*C.char, len(values)) for i, v := range values { cValues[i] = C.CString(v) } - C.batch_push_string_array(s.L, &cValues[0], C.int(len(values))) + C.batch_push_string_array_fast(s.L, &cValues[0], C.int(len(values))) for i := range values { C.free(unsafe.Pointer(cValues[i])) @@ -372,96 +237,126 @@ func (s *State) BatchPushStringArray(values []string) error { return nil } -// BatchPushBoolArray creates and pushes a bool array in a single C call func (s *State) BatchPushBoolArray(values []bool) error { if len(values) == 0 { s.CreateTable(0, 0) return nil } - cValues := make([]C.int, len(values)) + s.CreateTable(len(values), 0) for i, v := range values { - if v { - cValues[i] = 1 - } else { - cValues[i] = 0 - } + s.PushBoolean(v) + C.lua_rawseti(s.L, -2, C.int(i+1)) } - - C.batch_push_bool_array(s.L, &cValues[0], C.int(len(values))) return nil } -// BatchExtractIntArray extracts an entire int array in a single C call func (s *State) BatchExtractIntArray(index, length int) ([]int, error) { if length <= 0 { return []int{}, nil } - buffer := make([]C.int, length) - C.batch_extract_int_array(s.L, C.int(index), &buffer[0], C.int(length)) - + absIdx := s.absIndex(index) result := make([]int, length) - for i := range length { - result[i] = int(buffer[i]) + + for i := 0; i < length; i++ { + C.lua_rawgeti(s.L, C.int(absIdx), C.int(i+1)) + result[i] = int(s.ToNumber(-1)) + s.Pop(1) } return result, nil } -// BatchExtractFloatArray extracts an entire float array in a single C call func (s *State) BatchExtractFloatArray(index, length int) ([]float64, error) { if length <= 0 { return []float64{}, nil } - buffer := make([]C.double, length) - C.batch_extract_float_array(s.L, C.int(index), &buffer[0], C.int(length)) - + absIdx := s.absIndex(index) result := make([]float64, length) - for i := range length { - result[i] = float64(buffer[i]) + + for i := 0; i < length; i++ { + C.lua_rawgeti(s.L, C.int(absIdx), C.int(i+1)) + result[i] = s.ToNumber(-1) + s.Pop(1) } return result, nil } -// BatchExtractStringArray extracts an entire string array in a single C call func (s *State) BatchExtractStringArray(index, length int) ([]string, error) { if length <= 0 { return []string{}, nil } - buffer := make([]*C.char, length) - C.batch_extract_string_array(s.L, C.int(index), &buffer[0], C.int(length)) - + absIdx := s.absIndex(index) result := make([]string, length) - for i := range length { - result[i] = C.GoString(buffer[i]) - C.free(unsafe.Pointer(buffer[i])) + + for i := 0; i < length; i++ { + C.lua_rawgeti(s.L, C.int(absIdx), C.int(i+1)) + result[i] = s.ToString(-1) + s.Pop(1) } return result, nil } -// BatchExtractBoolArray extracts an entire bool array in a single C call func (s *State) BatchExtractBoolArray(index, length int) ([]bool, error) { if length <= 0 { return []bool{}, nil } - buffer := make([]C.int, length) - C.batch_extract_bool_array(s.L, C.int(index), &buffer[0], C.int(length)) - + absIdx := s.absIndex(index) result := make([]bool, length) - for i := range length { - result[i] = buffer[i] != 0 + + for i := 0; i < length; i++ { + C.lua_rawgeti(s.L, C.int(absIdx), C.int(i+1)) + result[i] = s.ToBoolean(-1) + s.Pop(1) } return result, nil } -// BatchTableBuilder provides batched table building operations +func (s *State) BatchSetGlobals(globals map[string]string) error { + for k, v := range globals { + s.PushString(v) + s.SetGlobal(k) + } + return nil +} + +func (s *State) BatchGetGlobals(names []string) error { + for _, name := range names { + s.GetGlobal(name) + } + return nil +} + +func (s *State) BatchGetTableFields(tableIndex int, keys []string) error { + absIdx := s.absIndex(tableIndex) + for _, key := range keys { + s.GetField(absIdx, key) + } + return nil +} + +type TypeCheck struct { + Index int + ExpectedType LuaType +} + +func (s *State) BatchCheckTypes(checks []TypeCheck) error { + for _, check := range checks { + if s.GetType(check.Index) != check.ExpectedType { + return fmt.Errorf("type mismatch at index %d: expected %s, got %s", + check.Index, check.ExpectedType, s.GetType(check.Index)) + } + } + return nil +} + type BatchTableBuilder struct { state *State index int @@ -471,7 +366,6 @@ type BatchTableBuilder struct { otherFields map[string]any } -// NewBatchTableBuilder creates a table builder that batches operations func (s *State) NewBatchTableBuilder() *BatchTableBuilder { s.NewTable() return &BatchTableBuilder{ @@ -484,319 +378,59 @@ func (s *State) NewBatchTableBuilder() *BatchTableBuilder { } } -// SetString queues a string field for batch setting func (tb *BatchTableBuilder) SetString(key, value string) *BatchTableBuilder { tb.stringFields[key] = value return tb } -// SetNumber queues a number field for batch setting func (tb *BatchTableBuilder) SetNumber(key string, value float64) *BatchTableBuilder { tb.numberFields[key] = value return tb } -// SetBool queues a boolean field for batch setting func (tb *BatchTableBuilder) SetBool(key string, value bool) *BatchTableBuilder { tb.boolFields[key] = value return tb } -// SetNil sets a nil field immediately func (tb *BatchTableBuilder) SetNil(key string) *BatchTableBuilder { tb.state.PushNil() tb.state.SetField(tb.index, key) return tb } -// SetTable sets a table field immediately func (tb *BatchTableBuilder) SetTable(key string, value any) *BatchTableBuilder { tb.otherFields[key] = value return tb } -// SetArray sets an array field immediately func (tb *BatchTableBuilder) SetArray(key string, values []any) *BatchTableBuilder { tb.otherFields[key] = values return tb } -// Build executes all batched operations and finalizes the table func (tb *BatchTableBuilder) Build() error { - // Batch string fields if len(tb.stringFields) > 0 { - if err := tb.state.BatchSetStringFields(tb.index, tb.stringFields); err != nil { - return err - } + tb.state.BatchSetStringFields(tb.index, tb.stringFields) } - - // Batch number fields if len(tb.numberFields) > 0 { - if err := tb.state.BatchSetNumberFields(tb.index, tb.numberFields); err != nil { - return err - } + tb.state.BatchSetNumberFields(tb.index, tb.numberFields) } - - // Batch boolean fields if len(tb.boolFields) > 0 { - if err := tb.state.BatchSetBoolFields(tb.index, tb.boolFields); err != nil { - return err - } + tb.state.BatchSetBoolFields(tb.index, tb.boolFields) } - - // Individual operations for complex types for key, value := range tb.otherFields { - if err := tb.state.PushValue(value); err != nil { - return err - } + tb.state.PushValue(value) tb.state.SetField(tb.index, key) } - return nil } -// BatchSetGlobals sets multiple global variables in one C call -func (s *State) BatchSetGlobals(globals map[string]string) error { - if len(globals) == 0 { - return nil - } - - names := make([]*C.char, len(globals)) - values := make([]*C.char, len(globals)) - - i := 0 - for name, value := range globals { - names[i] = C.CString(name) - values[i] = C.CString(value) - i++ - } - - C.batch_set_globals(s.L, &names[0], &values[0], C.int(len(globals))) - - for j := range len(globals) { - C.free(unsafe.Pointer(names[j])) - C.free(unsafe.Pointer(values[j])) - } - - return nil -} - -// BatchGetGlobals gets multiple global variables, leaving them on stack -func (s *State) BatchGetGlobals(names []string) error { - if len(names) == 0 { - return nil - } - - cNames := make([]*C.char, len(names)) - for i, name := range names { - cNames[i] = C.CString(name) - } - - C.batch_get_globals(s.L, &cNames[0], C.int(len(names))) - - for i := range names { - C.free(unsafe.Pointer(cNames[i])) - } - - return nil -} - -// BatchGetTableFields gets multiple fields from a table, leaving them on stack -func (s *State) BatchGetTableFields(tableIndex int, keys []string) error { - if len(keys) == 0 { - return nil - } - - cKeys := make([]*C.char, len(keys)) - for i, key := range keys { - cKeys[i] = C.CString(key) - } - - C.batch_get_table_fields(s.L, C.int(tableIndex), &cKeys[0], C.int(len(keys))) - - for i := range keys { - C.free(unsafe.Pointer(cKeys[i])) - } - - return nil -} - -// BatchCheckTypes validates multiple stack positions have expected types -func (s *State) BatchCheckTypes(checks []TypeCheck) error { - if len(checks) == 0 { - return nil - } - - indices := make([]C.int, len(checks)) - expectedTypes := make([]C.int, len(checks)) - - for i, check := range checks { - indices[i] = C.int(check.Index) - expectedTypes[i] = C.int(check.ExpectedType) - } - - result := C.batch_check_types(s.L, &indices[0], &expectedTypes[0], C.int(len(checks))) - if result != 0 { - check := checks[result-1] - actual := s.GetType(check.Index) - return fmt.Errorf("type mismatch at index %d: expected %s, got %s", - check.Index, check.ExpectedType, actual) - } - - return nil -} - -// BatchAddPackagePaths adds multiple paths to package.path in one operation -func (s *State) BatchAddPackagePaths(paths []string) error { - if len(paths) == 0 { - return nil - } - - cPaths := make([]*C.char, len(paths)) - for i, path := range paths { - normalizedPath := strings.ReplaceAll(path, "\\", "/") - cPaths[i] = C.CString(normalizedPath) - } - - C.batch_add_package_paths(s.L, &cPaths[0], C.int(len(paths))) - - for i := range paths { - C.free(unsafe.Pointer(cPaths[i])) - } - - return nil -} - -// TypeCheck represents a type validation check -type TypeCheck struct { - Index int - ExpectedType LuaType -} - -// BatchValuePusher accumulates values for efficient batch pushing -type BatchValuePusher struct { - state *State - values []any -} - -// NewBatchValuePusher creates a new batch value pusher -func (s *State) NewBatchValuePusher() *BatchValuePusher { - return &BatchValuePusher{ - state: s, - values: make([]any, 0, 8), - } -} - -// Add queues a value for batch pushing -func (bvp *BatchValuePusher) Add(value any) *BatchValuePusher { - bvp.values = append(bvp.values, value) - return bvp -} - -// Push executes the batch push operation -func (bvp *BatchValuePusher) Push() error { - if len(bvp.values) == 0 { - return nil - } - - // Separate by type for optimal batching - var strings []string - var numbers []float64 - var bools []bool - var others []any - - for _, v := range bvp.values { - switch val := v.(type) { - case string: - if len(strings) == len(numbers)+len(bools)+len(others) { - strings = append(strings, val) - } else { - others = append(others, val) - } - case int: - if len(numbers) == len(strings)+len(bools)+len(others) { - numbers = append(numbers, float64(val)) - } else { - others = append(others, val) - } - case float64: - if len(numbers) == len(strings)+len(bools)+len(others) { - numbers = append(numbers, val) - } else { - others = append(others, val) - } - case bool: - if len(bools) == len(strings)+len(numbers)+len(others) { - bools = append(bools, val) - } else { - others = append(others, val) - } - default: - others = append(others, val) - } - } - - // Push in batches when beneficial - if len(strings) > 2 && len(numbers) == 0 && len(bools) == 0 && len(others) == 0 { - return bvp.pushStringBatch(strings) - } - if len(numbers) > 2 && len(strings) == 0 && len(bools) == 0 && len(others) == 0 { - return bvp.pushNumberBatch(numbers) - } - if len(bools) > 2 && len(strings) == 0 && len(numbers) == 0 && len(others) == 0 { - return bvp.pushBoolBatch(bools) - } - - // Fall back to individual pushes for mixed types - for _, value := range bvp.values { - if err := bvp.state.PushValue(value); err != nil { - return err - } - } - - return nil -} - -func (bvp *BatchValuePusher) pushStringBatch(strings []string) error { - cValues := make([]*C.char, len(strings)) - for i, s := range strings { - cValues[i] = C.CString(s) - } - - for i, cstr := range cValues { - C.lua_pushstring(bvp.state.L, cstr) - C.free(unsafe.Pointer(cstr)) - _ = i // Use i to avoid unused variable - } - - return nil -} - -func (bvp *BatchValuePusher) pushNumberBatch(numbers []float64) error { - for _, n := range numbers { - C.lua_pushnumber(bvp.state.L, C.lua_Number(n)) - } - return nil -} - -func (bvp *BatchValuePusher) pushBoolBatch(bools []bool) error { - for _, b := range bools { - if b { - C.lua_pushboolean(bvp.state.L, 1) - } else { - C.lua_pushboolean(bvp.state.L, 0) - } - } - return nil -} - -// BatchTableReader efficiently reads multiple table configurations type BatchTableReader struct { state *State index int } -// NewBatchTableReader creates a table reader for efficient field access func (s *State) NewBatchTableReader(index int) *BatchTableReader { return &BatchTableReader{ state: s, @@ -804,31 +438,7 @@ func (s *State) NewBatchTableReader(index int) *BatchTableReader { } } -// ReadFields reads multiple fields and returns them as a map func (btr *BatchTableReader) ReadFields(keys []string) (map[string]any, error) { - if len(keys) == 0 { - return make(map[string]any), nil - } - - // Use batch operation for many fields - if len(keys) > 3 { - startTop := btr.state.GetTop() - if err := btr.state.BatchGetTableFields(btr.index, keys); err != nil { - return nil, err - } - - result := make(map[string]any, len(keys)) - for i, key := range keys { - if value, err := btr.state.ToValue(startTop + i + 1); err == nil { - result[key] = value - } - } - - btr.state.SetTop(startTop) // Restore stack - return result, nil - } - - // Individual access for few fields result := make(map[string]any, len(keys)) for _, key := range keys { btr.state.GetField(btr.index, key) @@ -837,18 +447,15 @@ func (btr *BatchTableReader) ReadFields(keys []string) (map[string]any, error) { } btr.state.Pop(1) } - return result, nil } -// BatchGlobalManager manages multiple global operations efficiently type BatchGlobalManager struct { state *State pendingSets map[string]string pendingGets []string } -// NewBatchGlobalManager creates a global variable manager func (s *State) NewBatchGlobalManager() *BatchGlobalManager { return &BatchGlobalManager{ state: s, @@ -857,34 +464,25 @@ func (s *State) NewBatchGlobalManager() *BatchGlobalManager { } } -// QueueSet queues a global variable for batch setting func (bgm *BatchGlobalManager) QueueSet(name, value string) *BatchGlobalManager { bgm.pendingSets[name] = value return bgm } -// QueueGet queues a global variable for batch getting func (bgm *BatchGlobalManager) QueueGet(name string) *BatchGlobalManager { bgm.pendingGets = append(bgm.pendingGets, name) return bgm } -// Execute performs all queued operations func (bgm *BatchGlobalManager) Execute() (map[string]any, error) { - // Execute batch sets if len(bgm.pendingSets) > 0 { - if err := bgm.state.BatchSetGlobals(bgm.pendingSets); err != nil { - return nil, err - } + bgm.state.BatchSetGlobals(bgm.pendingSets) } - // Execute batch gets var result map[string]any if len(bgm.pendingGets) > 0 { startTop := bgm.state.GetTop() - if err := bgm.state.BatchGetGlobals(bgm.pendingGets); err != nil { - return nil, err - } + bgm.state.BatchGetGlobals(bgm.pendingGets) result = make(map[string]any, len(bgm.pendingGets)) for i, name := range bgm.pendingGets { @@ -892,9 +490,33 @@ func (bgm *BatchGlobalManager) Execute() (map[string]any, error) { result[name] = value } } - - bgm.state.SetTop(startTop) // Restore stack + bgm.state.SetTop(startTop) } - return result, nil } + +type BatchValuePusher struct { + state *State + values []any +} + +func (s *State) NewBatchValuePusher() *BatchValuePusher { + return &BatchValuePusher{ + state: s, + values: make([]any, 0), + } +} + +func (bvp *BatchValuePusher) Add(value any) *BatchValuePusher { + bvp.values = append(bvp.values, value) + return bvp +} + +func (bvp *BatchValuePusher) Push() error { + for _, value := range bvp.values { + if err := bvp.state.PushValue(value); err != nil { + return err + } + } + return nil +} diff --git a/bench/bench_test.go b/bench/bench_test.go index 356de95..085088c 100644 --- a/bench/bench_test.go +++ b/bench/bench_test.go @@ -470,3 +470,316 @@ func BenchmarkMultipleExecutions(b *testing.B) { } }) } + +// BenchmarkBatchVsIndividualTableFields compares batch vs individual table field setting +func BenchmarkBatchVsIndividualTableFields(b *testing.B) { + b.Run("Individual", func(b *testing.B) { + state := luajit.New() + defer state.Close() + + fields := map[string]string{ + "name": "test", + "version": "1.0", + "description": "benchmark", + "author": "user", + "license": "MIT", + "homepage": "example.com", + "repository": "git://example.com", + "keywords": "test,bench", + } + + TrackMemoryUsage(b, "individual-fields", func() { + for i := 0; i < b.N; i++ { + tb := state.NewTableBuilder() + for k, v := range fields { + tb.SetString(k, v) + } + tb.Build() + state.Pop(1) + } + }) + }) + + b.Run("Batch", func(b *testing.B) { + state := luajit.New() + defer state.Close() + + fields := map[string]string{ + "name": "test", + "version": "1.0", + "description": "benchmark", + "author": "user", + "license": "MIT", + "homepage": "example.com", + "repository": "git://example.com", + "keywords": "test,bench", + } + + TrackMemoryUsage(b, "batch-fields", func() { + for i := 0; i < b.N; i++ { + btb := state.NewBatchTableBuilder() + for k, v := range fields { + btb.SetString(k, v) + } + btb.Build() + state.Pop(1) + } + }) + }) +} + +// BenchmarkBatchVsIndividualArrays compares batch vs individual array creation +func BenchmarkBatchVsIndividualArrays(b *testing.B) { + intArray := make([]int, 100) + for i := range intArray { + intArray[i] = i * 2 + } + + b.Run("Individual", func(b *testing.B) { + state := luajit.New() + defer state.Close() + + TrackMemoryUsage(b, "individual-array", func() { + for i := 0; i < b.N; i++ { + state.CreateTable(len(intArray), 0) + for j, v := range intArray { + state.PushNumber(float64(j + 1)) + state.PushNumber(float64(v)) + state.SetTable(-3) + } + state.Pop(1) + } + }) + }) + + b.Run("Batch", func(b *testing.B) { + state := luajit.New() + defer state.Close() + + TrackMemoryUsage(b, "batch-array", func() { + for i := 0; i < b.N; i++ { + state.BatchPushIntArray(intArray) + state.Pop(1) + } + }) + }) +} + +// BenchmarkBatchVsIndividualGlobals compares batch vs individual global variable setting +func BenchmarkBatchVsIndividualGlobals(b *testing.B) { + globals := map[string]string{ + "APP_NAME": "myapp", + "APP_VERSION": "1.0.0", + "APP_ENVIRONMENT": "production", + "APP_DEBUG": "false", + "APP_URL": "https://example.com", + "APP_TIMEZONE": "UTC", + "DB_HOST": "localhost", + "DB_PORT": "5432", + } + + b.Run("Individual", func(b *testing.B) { + state := luajit.New() + defer state.Close() + + TrackMemoryUsage(b, "individual-globals", func() { + for i := 0; i < b.N; i++ { + for k, v := range globals { + state.PushString(v) + state.SetGlobal(k) + } + } + }) + }) + + b.Run("Batch", func(b *testing.B) { + state := luajit.New() + defer state.Close() + + TrackMemoryUsage(b, "batch-globals", func() { + for i := 0; i < b.N; i++ { + state.BatchSetGlobals(globals) + } + }) + }) +} + +// BenchmarkBatchVsIndividualTableReads compares batch vs individual table field reading +func BenchmarkBatchVsIndividualTableReads(b *testing.B) { + state := luajit.New() + defer state.Close() + + // Setup test table + setupCode := ` + config = { + database = {host = "localhost", port = 5432, name = "mydb"}, + server = {host = "0.0.0.0", port = 8080, ssl = true}, + logging = {level = "info", file = "/var/log/app.log"}, + cache = {enabled = true, ttl = 300, size = 1000} + } + ` + if err := state.DoString(setupCode); err != nil { + b.Fatalf("Setup failed: %v", err) + } + + keys := []string{"database", "server", "logging", "cache"} + + b.Run("Individual", func(b *testing.B) { + TrackMemoryUsage(b, "individual-reads", func() { + for i := 0; i < b.N; i++ { + state.GetGlobal("config") + values := make(map[string]any) + for _, key := range keys { + state.GetField(-1, key) + if val, err := state.ToValue(-1); err == nil { + values[key] = val + } + state.Pop(1) + } + state.Pop(1) + _ = values + } + }) + }) + + b.Run("Batch", func(b *testing.B) { + TrackMemoryUsage(b, "batch-reads", func() { + for i := 0; i < b.N; i++ { + state.GetGlobal("config") + reader := state.NewBatchTableReader(-1) + values, _ := reader.ReadFields(keys) + state.Pop(1) + _ = values + } + }) + }) +} + +// BenchmarkBatchValuePusher compares batch vs individual value pushing +func BenchmarkBatchValuePusher(b *testing.B) { + values := []any{"hello", 42, true, 3.14, "world", 100, false, 2.718} + + b.Run("Individual", func(b *testing.B) { + state := luajit.New() + defer state.Close() + + TrackMemoryUsage(b, "individual-push", func() { + for i := 0; i < b.N; i++ { + for _, v := range values { + state.PushValue(v) + } + state.Pop(len(values)) + } + }) + }) + + b.Run("Batch", func(b *testing.B) { + state := luajit.New() + defer state.Close() + + TrackMemoryUsage(b, "batch-push", func() { + for i := 0; i < b.N; i++ { + pusher := state.NewBatchValuePusher() + for _, v := range values { + pusher.Add(v) + } + pusher.Push() + state.Pop(len(values)) + } + }) + }) +} + +// BenchmarkBatchTypeChecking compares batch vs individual type checking +func BenchmarkBatchTypeChecking(b *testing.B) { + state := luajit.New() + defer state.Close() + + // Setup test values on stack + state.PushString("hello") + state.PushNumber(42) + state.PushBoolean(true) + state.PushNil() + + checks := []luajit.TypeCheck{ + {Index: 1, ExpectedType: luajit.TypeString}, + {Index: 2, ExpectedType: luajit.TypeNumber}, + {Index: 3, ExpectedType: luajit.TypeBoolean}, + {Index: 4, ExpectedType: luajit.TypeNil}, + } + + b.Run("Individual", func(b *testing.B) { + TrackMemoryUsage(b, "individual-check", func() { + for i := 0; i < b.N; i++ { + for _, check := range checks { + if state.GetType(check.Index) != check.ExpectedType { + b.Fatal("Type mismatch") + } + } + } + }) + }) + + b.Run("Batch", func(b *testing.B) { + TrackMemoryUsage(b, "batch-check", func() { + for i := 0; i < b.N; i++ { + if err := state.BatchCheckTypes(checks); err != nil { + b.Fatal("Type check failed:", err) + } + } + }) + }) +} + +// BenchmarkBatchGlobalManager benchmarks the batch global manager +func BenchmarkBatchGlobalManager(b *testing.B) { + state := luajit.New() + defer state.Close() + + setGlobals := map[string]string{ + "VAR1": "value1", + "VAR2": "value2", + "VAR3": "value3", + "VAR4": "value4", + } + + getGlobals := []string{"VAR1", "VAR2", "VAR3", "VAR4"} + + b.Run("Individual", func(b *testing.B) { + TrackMemoryUsage(b, "individual-global-mgmt", func() { + for i := 0; i < b.N; i++ { + // Set globals + for k, v := range setGlobals { + state.PushString(v) + state.SetGlobal(k) + } + // Get globals + results := make(map[string]any) + for _, name := range getGlobals { + state.GetGlobal(name) + if val, err := state.ToValue(-1); err == nil { + results[name] = val + } + state.Pop(1) + } + _ = results + } + }) + }) + + b.Run("Batch", func(b *testing.B) { + TrackMemoryUsage(b, "batch-global-mgmt", func() { + for i := 0; i < b.N; i++ { + mgr := state.NewBatchGlobalManager() + for k, v := range setGlobals { + mgr.QueueSet(k, v) + } + for _, name := range getGlobals { + mgr.QueueGet(name) + } + results, _ := mgr.Execute() + _ = results + } + }) + }) +} diff --git a/bench/ezbench_test.go b/bench/ezbench_test.go index 5e570d2..d1daf55 100644 --- a/bench/ezbench_test.go +++ b/bench/ezbench_test.go @@ -136,3 +136,637 @@ func BenchmarkLuaBytecodeExecution(b *testing.B) { }) } } + +// 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 + } + }) + }) +} diff --git a/bench/snapshot.txt b/bench/snapshot.txt new file mode 100644 index 0000000..be3a8a0 --- /dev/null +++ b/bench/snapshot.txt @@ -0,0 +1,27 @@ +cpu: AMD Ryzen 7 8845HS w/ Radeon 780M Graphics +BenchmarkSimpleDoString-16 3050360 384.6 ns/op 0 dostring-bytes/op 0 dostring-mallocs +BenchmarkSimpleCompileAndRun-16 3016736 386.8 ns/op 0 compile-run-bytes/op 0 compile-run-mallocs +BenchmarkSimpleCompileLoadRun-16 1264791 933.1 ns/op 72.00 compile-load-run-bytes/op 2529585 compile-load-run-mallocs +BenchmarkSimplePrecompiledBytecode-16 6726292 185.7 ns/op 0 precompiled-bytes/op 0 precompiled-mallocs +BenchmarkFunctionCallDoString-16 2349952 518.0 ns/op 0 func-dostring-bytes/op 0 func-dostring-mallocs +BenchmarkFunctionCallPrecompiled-16 6023224 207.9 ns/op 0 func-precompiled-bytes/op 0 func-precompiled-mallocs +BenchmarkLoopDoString-16 149439 7880 ns/op 0 loop-dostring-bytes/op 0 loop-dostring-mallocs +BenchmarkLoopPrecompiled-16 166184 6926 ns/op 0 loop-precompiled-bytes/op 0 loop-precompiled-mallocs +BenchmarkTableOperationsDoString-16 66718 17634 ns/op 0 table-dostring-bytes/op 0 table-dostring-mallocs +BenchmarkTableOperationsPrecompiled-16 74943 15629 ns/op 0 table-precompiled-bytes/op 0 table-precompiled-mallocs +BenchmarkGoFunctionCall-16 2407584 475.5 ns/op 0.001120 go-func-call-bytes/op 8.000 go-func-call-mallocs +BenchmarkComplexScript-16 37376 32377 ns/op 8.000 complex-script-bytes/op 37376 complex-script-mallocs +BenchmarkComplexScriptPrecompiled-16 50720 23908 ns/op 0 complex-precompiled-bytes/op 0 complex-precompiled-mallocs +BenchmarkMultipleExecutions-16 4314085 273.9 ns/op 0 multiple-executions-bytes/op 0 multiple-executions-mallocs +BenchmarkLuaDirectExecution/SimpleAddition-16 2614246 451.6 ns/op 0 direct-SimpleAddition-bytes/op 0 direct-SimpleAddition-mallocs +BenchmarkLuaDirectExecution/LoopSum-16 144027 8119 ns/op 0 direct-LoopSum-bytes/op 0 direct-LoopSum-mallocs +BenchmarkLuaDirectExecution/FunctionCall-16 159513 7507 ns/op 0 direct-FunctionCall-bytes/op 0 direct-FunctionCall-mallocs +BenchmarkLuaDirectExecution/TableCreation-16 134306 9002 ns/op 0 direct-TableCreation-bytes/op 0 direct-TableCreation-mallocs +BenchmarkLuaDirectExecution/StringOperations-16 405411 2730 ns/op 0 direct-StringOperations-bytes/op 0 direct-StringOperations-mallocs +BenchmarkLuaBytecodeExecution/SimpleAddition-16 6323188 186.6 ns/op 219.73 MB/s 0 bytecode-SimpleAddition-bytes/op 0 bytecode-SimpleAddition-mallocs +BenchmarkLuaBytecodeExecution/LoopSum-16 162441 7137 ns/op 29.98 MB/s 0 bytecode-LoopSum-bytes/op 0 bytecode-LoopSum-mallocs +BenchmarkLuaBytecodeExecution/FunctionCall-16 175790 6386 ns/op 35.70 MB/s 0 bytecode-FunctionCall-bytes/op 0 bytecode-FunctionCall-mallocs +BenchmarkLuaBytecodeExecution/TableCreation-16 152374 7534 ns/op 29.47 MB/s 0 bytecode-TableCreation-bytes/op 0 bytecode-TableCreation-mallocs +BenchmarkLuaBytecodeExecution/StringOperations-16 705818 1548 ns/op 158.23 MB/s 0 bytecode-StringOperations-bytes/op 0 bytecode-StringOperations-mallocs +PASS +ok git.sharkk.net/Sky/LuaJIT-to-Go/bench 33.958s