From ef0d86b0ce64d4be2d0b99f3ca983977bca8a794 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Thu, 2 Jan 2025 02:14:46 +0700 Subject: [PATCH] tagged table pools, table.new/clear to speed them up --- README.md | 2 +- ROADMAP.md | 2 +- develop/untests.lua | 28 ++++++ evolved.lua | 209 ++++++++++++++++++++++++++++++-------------- 4 files changed, 173 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index c677192..3c59ae7 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ batch_clear :: query -> integer, boolean batch_destroy :: query -> integer, boolean chunk :: fragment... -> chunk?, entity[]? -select :: chunk, fragment... -> component[]?... +select :: chunk, fragment... -> component[]... each :: entity -> {each_state? -> fragment?, component?}, each_state? execute :: query -> {execute_state? -> chunk?, entity[]?}, execute_state? diff --git a/ROADMAP.md b/ROADMAP.md index c8c74e8..6115f58 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,6 +7,6 @@ - every chunk can hold has_on_assign/has_on_insert/has_on_remove tags - optimize batch operations for cases with moving entities to empty chunks - should we clear chunk's components by on_insert tag callback? -- use table.new/clear for cached tables - replace id type aliases with separated types to hide implementation details - clear chunk's tables instead reallocating them +- add initializer list to __table_new? diff --git a/develop/untests.lua b/develop/untests.lua index d8eb678..e604b82 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -2256,6 +2256,34 @@ do end end +do + local f1, f2, f3 = evo.id(3) + + local q = evo.id() + evo.set(q, evo.INCLUDE_LIST, f1) + + local e1 = evo.id() + assert(evo.insert(e1, f1, 41)) + assert(evo.insert(e1, f2, 42)) + + local e2 = evo.id() + assert(evo.insert(e2, f1, 43)) + assert(evo.insert(e2, f3, 44)) + + do + local entity_sum = 0 + + for _, entities in evo.execute(q) do + assert(#entities > 0) + for _, e in ipairs(entities) do + entity_sum = entity_sum + e + end + end + + assert(entity_sum == e1 + e2) + end +end + do local f1, f2 = evo.id(2) diff --git a/evolved.lua b/evolved.lua index 9cb6700..7dabdaa 100644 --- a/evolved.lua +++ b/evolved.lua @@ -44,15 +44,15 @@ local evolved = { ---@field package __without_fragment_edges table ---@class (exact) evolved.each_state ----@field [1] integer structural_changes ----@field [2] evolved.chunk entity_chunk ----@field [3] integer entity_place ----@field [4] evolved.fragment? fragments_index +---@field package [1] integer structural_changes +---@field package [2] evolved.chunk entity_chunk +---@field package [3] integer entity_place +---@field package [4] evolved.fragment? fragment_index ---@class (exact) evolved.execute_state ----@field [1] integer structural_changes ----@field [2] evolved.chunk[] chunk_stack ----@field [3] table exclude_set +---@field package [1] integer structural_changes +---@field package [2] evolved.chunk[] chunk_stack +---@field package [3] table exclude_set ---@alias evolved.each_iterator fun(state: evolved.each_state?): evolved.fragment?, evolved.component? ---@alias evolved.execute_iterator fun(state: evolved.execute_state?): evolved.chunk?, evolved.entity[]? @@ -76,7 +76,6 @@ local __major_chunks = {} ---@type table local __entity_chunks = {} ---@type table local __entity_places = {} ---@type table -local __table_cache = {} ---@type table[] local __structural_changes = 0 ---@type integer --- @@ -85,29 +84,69 @@ local __structural_changes = 0 ---@type integer --- --- ----@diagnostic disable-next-line -local __table_move = table.move or function(a1, f, e, t, a2) - if a2 == nil then - a2 = a1 - end +local __table_move = (function() + ---@param a1 table + ---@param f integer + ---@param e integer + ---@param t integer + ---@param a2? table + ---@return table a2 + return table.move or function(a1, f, e, t, a2) + -- REFERENCE: + -- https://github.com/LuaJIT/LuaJIT/blob/v2.1/src/lib_table.c#L132 + + if a2 == nil then + a2 = a1 + end + + if e < f then + return a2 + end + + local d = t - f + + if t > e or t <= f or a2 ~= a1 then + for i = f, e do + a2[i + d] = a1[i] + end + else + for i = e, f, -1 do + a2[i + d] = a1[i] + end + end - if e < f then return a2 end +end)() - local d = t - f +local __table_unpack = (function() + return table.unpack or unpack +end)() - if t > e or t <= f or a2 ~= a1 then - for i = f, e do a2[i + d] = a1[i] end - else - for i = e, f, -1 do a2[i + d] = a1[i] end +---@type fun(narray: integer, nhash: integer): table +local __table_new = (function() + local table_new_loader = package.preload['table.new'] + ---@param narray integer + ---@param nhash integer + ---@return table + return table_new_loader and table_new_loader() or function(narray, nhash) + if type(narray) ~= 'number' then error('narray must be a number', 2) end + if type(nhash) ~= 'number' then error('nhash must be a number', 2) end + return {} end +end)() - return a2 -end +---@type fun(tab: table) +local __table_clear = (function() + local table_clear_loader = package.preload['table.clear'] + ---@param tab table + return table_clear_loader and table_clear_loader() or function(tab) + if type(tab) ~= 'table' then error('tab must be a table', 2) end + for i = #tab, 1, -1 do tab[i] = nil end + for k in pairs(tab) do tab[k] = nil end + end +end)() ----@diagnostic disable-next-line -local __table_unpack = table.unpack or unpack --- --- @@ -198,26 +237,61 @@ end --- --- +local __TABLE_POOL_TAG__BYTECODE = 1 +local __TABLE_POOL_TAG__CHUNK_LIST = 2 +local __TABLE_POOL_TAG__EACH_STATE = 3 +local __TABLE_POOL_TAG__EXECUTE_STATE = 4 +local __TABLE_POOL_TAG__FRAGMENT_LIST = 5 + +---@alias evolved.table_pool_tag +---| `__TABLE_POOL_TAG__BYTECODE` +---| `__TABLE_POOL_TAG__CHUNK_LIST` +---| `__TABLE_POOL_TAG__EACH_STATE` +---| `__TABLE_POOL_TAG__EXECUTE_STATE` +---| `__TABLE_POOL_TAG__FRAGMENT_LIST` + +---@type table +local __tagged_table_pools = __table_new(5, 0) + +---@param tag evolved.table_pool_tag +---@param narray integer +---@param nhash integer ---@return table ---@nodiscard -local function __acquire_table() - local table_cache_size = #__table_cache +local function __acquire_table(tag, narray, nhash) + local table_pool = __tagged_table_pools[tag] - if table_cache_size == 0 then - return {} + if not table_pool then + table_pool = __table_new(16, 0) + __tagged_table_pools[tag] = table_pool end - local table = __table_cache[table_cache_size] - __table_cache[table_cache_size] = nil + local table_pool_size = #table_pool + + if table_pool_size == 0 then + return __table_new(narray, nhash) + end + + local table = table_pool[table_pool_size] + table_pool[table_pool_size] = nil return table end ----@param table table -local function __release_table(table) - for i = #table, 1, -1 do table[i] = nil end - for k in pairs(table) do table[k] = nil end - __table_cache[#__table_cache + 1] = table +---@param tag evolved.table_pool_tag +---@param tab table +local function __release_table(tag, tab) + local table_pool = __tagged_table_pools[tag] + + if not table_pool then + table_pool = __table_new(16, 0) + __tagged_table_pools[tag] = table_pool + end + + setmetatable(tab, nil) + __table_clear(tab) + + table_pool[#table_pool + 1] = tab end --- @@ -239,15 +313,13 @@ local function __each_iterator(each_state) error('structural changes are prohibited during iteration', 2) end - fragment_index = next(entity_chunk.__fragments, fragment_index) - if fragment_index then - each_state[4] = fragment_index + each_state[4] = next(entity_chunk.__fragments, fragment_index) local fragment_components = entity_chunk.__components[fragment_index] return fragment_index, fragment_components and fragment_components[entity_place] end - __release_table(each_state) + __release_table(__TABLE_POOL_TAG__EACH_STATE, each_state) end ---@type evolved.execute_iterator @@ -277,8 +349,8 @@ local function __execute_iterator(execute_state) end end - __release_table(chunk_stack) - __release_table(execute_state) + __release_table(__TABLE_POOL_TAG__CHUNK_LIST, chunk_stack) + __release_table(__TABLE_POOL_TAG__EXECUTE_STATE, execute_state) end --- @@ -1231,7 +1303,7 @@ local function __defer_commit() local bytecode = __defer_bytecode __defer_length = 0 - __defer_bytecode = __acquire_table() + __defer_bytecode = __acquire_table(__TABLE_POOL_TAG__BYTECODE, length, 0) local bytecode_index = 1 while bytecode_index <= length do @@ -1239,7 +1311,7 @@ local function __defer_commit() bytecode_index = bytecode_index + op(bytecode, bytecode_index + 1) + 1 end - __release_table(bytecode) + __release_table(__TABLE_POOL_TAG__BYTECODE, bytecode) return true end @@ -1966,7 +2038,7 @@ function evolved.batch_set(query, fragment, ...) end ---@type evolved.chunk[] - local chunk_list = __acquire_table() + local chunk_list = __acquire_table(__TABLE_POOL_TAG__CHUNK_LIST, 16, 0) for chunk in evolved.execute(query) do chunk_list[#chunk_list + 1] = chunk @@ -1986,7 +2058,7 @@ function evolved.batch_set(query, fragment, ...) end __defer_commit() - __release_table(chunk_list) + __release_table(__TABLE_POOL_TAG__CHUNK_LIST, chunk_list) return set_count, false end @@ -2002,7 +2074,7 @@ function evolved.batch_assign(query, fragment, ...) end ---@type evolved.chunk[] - local chunk_list = __acquire_table() + local chunk_list = __acquire_table(__TABLE_POOL_TAG__CHUNK_LIST, 16, 0) for chunk in evolved.execute(query) do chunk_list[#chunk_list + 1] = chunk @@ -2018,7 +2090,7 @@ function evolved.batch_assign(query, fragment, ...) end __defer_commit() - __release_table(chunk_list) + __release_table(__TABLE_POOL_TAG__CHUNK_LIST, chunk_list) return assigned_count, false end @@ -2034,7 +2106,7 @@ function evolved.batch_insert(query, fragment, ...) end ---@type evolved.chunk[] - local chunk_list = __acquire_table() + local chunk_list = __acquire_table(__TABLE_POOL_TAG__CHUNK_LIST, 16, 0) for chunk in evolved.execute(query) do chunk_list[#chunk_list + 1] = chunk @@ -2050,7 +2122,7 @@ function evolved.batch_insert(query, fragment, ...) end __defer_commit() - __release_table(chunk_list) + __release_table(__TABLE_POOL_TAG__CHUNK_LIST, chunk_list) return inserted_count, false end @@ -2065,7 +2137,7 @@ function evolved.batch_remove(query, ...) end ---@type evolved.chunk[] - local chunk_list = __acquire_table() + local chunk_list = __acquire_table(__TABLE_POOL_TAG__CHUNK_LIST, 16, 0) for chunk in evolved.execute(query) do chunk_list[#chunk_list + 1] = chunk @@ -2081,7 +2153,7 @@ function evolved.batch_remove(query, ...) end __defer_commit() - __release_table(chunk_list) + __release_table(__TABLE_POOL_TAG__CHUNK_LIST, chunk_list) return removed_count, false end @@ -2095,7 +2167,7 @@ function evolved.batch_clear(query) end ---@type evolved.chunk[] - local chunk_list = __acquire_table() + local chunk_list = __acquire_table(__TABLE_POOL_TAG__CHUNK_LIST, 16, 0) for chunk in evolved.execute(query) do chunk_list[#chunk_list + 1] = chunk @@ -2111,7 +2183,7 @@ function evolved.batch_clear(query) end __defer_commit() - __release_table(chunk_list) + __release_table(__TABLE_POOL_TAG__CHUNK_LIST, chunk_list) return cleared_count, false end @@ -2125,7 +2197,7 @@ function evolved.batch_destroy(query) end ---@type evolved.chunk[] - local chunk_list = __acquire_table() + local chunk_list = __acquire_table(__TABLE_POOL_TAG__CHUNK_LIST, 16, 0) for chunk in evolved.execute(query) do chunk_list[#chunk_list + 1] = chunk @@ -2141,7 +2213,7 @@ function evolved.batch_destroy(query) end __defer_commit() - __release_table(chunk_list) + __release_table(__TABLE_POOL_TAG__CHUNK_LIST, chunk_list) return destroyed_count, false end @@ -2259,36 +2331,36 @@ function evolved.chunk(...) end ---@type evolved.fragment[] - local sorted_fragment_list = __acquire_table() + local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_count, 0) for i = 1, fragment_count do local fragment = select(i, ...) - sorted_fragment_list[#sorted_fragment_list + 1] = fragment + fragment_list[#fragment_list + 1] = fragment end - table.sort(sorted_fragment_list) + table.sort(fragment_list) - local root_fragment = sorted_fragment_list[1] + local root_fragment = fragment_list[1] local chunk = __root_chunks[root_fragment] if not chunk then - __release_table(sorted_fragment_list) + __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) return end for i = 2, fragment_count do - local child_fragment = sorted_fragment_list[i] - if child_fragment > sorted_fragment_list[i - 1] then + local child_fragment = fragment_list[i] + if child_fragment > fragment_list[i - 1] then chunk = chunk.__with_fragment_edges[child_fragment] if not chunk then - __release_table(sorted_fragment_list) + __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) return end end end - __release_table(sorted_fragment_list) + __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_list) return chunk, chunk.__entities end @@ -2354,11 +2426,13 @@ function evolved.each(entity) return __each_iterator end - local each_state = __acquire_table() + ---@type evolved.each_state + local each_state = __acquire_table(__TABLE_POOL_TAG__EACH_STATE, 4, 0) each_state[1] = __structural_changes each_state[2] = chunk each_state[3] = place + each_state[4] = next(chunk.__fragments) return __each_iterator, each_state end @@ -2391,8 +2465,11 @@ function evolved.execute(query) return __execute_iterator end - local chunk_stack = __acquire_table() - local execute_state = __acquire_table() + ---@type evolved.chunk[] + local chunk_stack = __acquire_table(__TABLE_POOL_TAG__CHUNK_LIST, 16, 0) + + ---@type evolved.execute_state + local execute_state = __acquire_table(__TABLE_POOL_TAG__EXECUTE_STATE, 3, 0) execute_state[1] = __structural_changes execute_state[2] = chunk_stack