WoW:HOWTO: Use Tables Without Generating Extra Garbage: Difference between revisions
No edit summary |
m (Move page script moved page HOWTO: Use Tables Without Generating Extra Garbage to HOWTO: Use Tables Without Generating Extra Garbage without leaving a redirect) |
||
(2 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
{{wowlua}} | |||
Creating number and strings values is pretty efficient in Lua, Numbers do not have any additional data structure associated with them and are freed immediately when no longer used. Strings are hashed and stacked together with same strings, so creating 10000 copies of "Hello, world!" or creating and freeing it repeatedly will not produce garbage. Tables, however, always take memory when created that always becomes garbage when table is no longer used. If you do not want to create unnecessary garbage either avoid using tables unless it is really necessary or, in case several of your throw-away tables use same set of fields, or each new calculation uses superset of field of previous table, use only one table instead of creating new table for every calculation. For example, if you need to sort 20 values, display result and then sort another 30 and display them too, most people would write: | Creating number and strings values is pretty efficient in Lua, Numbers do not have any additional data structure associated with them and are freed immediately when no longer used. Strings are hashed and stacked together with same strings, so creating 10000 copies of "Hello, world!" or creating and freeing it repeatedly will not produce garbage. Tables, however, always take memory when created that always becomes garbage when table is no longer used. If you do not want to create unnecessary garbage either avoid using tables unless it is really necessary or, in case several of your throw-away tables use same set of fields, or each new calculation uses superset of field of previous table, use only one table instead of creating new table for every calculation. For example, if you need to sort 20 values, display result and then sort another 30 and display them too, most people would write: | ||
Line 63: | Line 64: | ||
info.text = "Action Button1" | info.text = "Action Button1" | ||
info.value = "Value to act on" | info.value = "Value to act on" | ||
info.func = DoSomething | info.func = function() DoSomething() end; | ||
UIDropDownMenu_AddButton(info); | UIDropDownMenu_AddButton(info); | ||
Line 69: | Line 70: | ||
info.text = "Action Button2" | info.text = "Action Button2" | ||
info.value = "Another value to act on" | info.value = "Another value to act on" | ||
info.func = DoSomethingElse | info.func = function() DoSomethingElse() end; | ||
UIDropDownMenu_AddButton(info); | UIDropDownMenu_AddButton(info); | ||
end | end | ||
Line 104: | Line 105: | ||
workingTableForInitMenu.text = "Action Button1" | workingTableForInitMenu.text = "Action Button1" | ||
workingTableForInitMenu.value = "Value to act on" | workingTableForInitMenu.value = "Value to act on" | ||
workingTableForInitMenu.func = DoSomething | workingTableForInitMenu.func = function() DoSomething() end; | ||
UIDropDownMenu_AddButton(workingTableForInitMenu) | UIDropDownMenu_AddButton(workingTableForInitMenu) | ||
workingTableForInitMenu.text = "Action Button2" | workingTableForInitMenu.text = "Action Button2" | ||
workingTableForInitMenu.value = "Another value to act on" | workingTableForInitMenu.value = "Another value to act on" | ||
workingTableForInitMenu.func = DoSomethingElse | workingTableForInitMenu.func = function() DoSomethingElse() end; | ||
UIDropDownMenu_AddButton(workingTableForInitMenu) | UIDropDownMenu_AddButton(workingTableForInitMenu) | ||
end | end | ||
Finally, let me stress once again: this works best when you have table of same size or with same set of fields. As you see from the last example, buttons differ by two fields and function now has to take care of that. Forgetting this while using this approach can produce some very funny and hard to track bugs. And clearing table from previous values can degrade performance if there are many fields to clear on each run. Another thing to be aware of is that unlike <tt>UIDropDownMenu_AddButton</tt>, some function may store table reference and check values from it at some point in future, hoping that nobody else uses this table. When such function do that in future only to get something else that was initially passed, they tend to mess things up in quite spectacular ways. Check the target function to make sure that it doesn't store passed table. | Finally, let me stress once again: this works best when you have table of same size or with same set of fields. As you see from the last example, buttons differ by two fields and function now has to take care of that. Forgetting this while using this approach can produce some very funny and hard to track bugs. And clearing table from previous values can degrade performance if there are many fields to clear on each run. Another thing to be aware of is that unlike <tt>UIDropDownMenu_AddButton</tt>, some function may store table reference and check values from it at some point in future, hoping that nobody else uses this table. When such function do that in future only to get something else that was initially passed, they tend to mess things up in quite spectacular ways. Check the target function to make sure that it doesn't store passed table. | ||
[[Category:HOWTOs|Use Tables Without Generating Extra Garbage]] | [[Category:HOWTOs|Use Tables Without Generating Extra Garbage]] |
Latest revision as of 04:48, 15 August 2023
← WoW Lua
Creating number and strings values is pretty efficient in Lua, Numbers do not have any additional data structure associated with them and are freed immediately when no longer used. Strings are hashed and stacked together with same strings, so creating 10000 copies of "Hello, world!" or creating and freeing it repeatedly will not produce garbage. Tables, however, always take memory when created that always becomes garbage when table is no longer used. If you do not want to create unnecessary garbage either avoid using tables unless it is really necessary or, in case several of your throw-away tables use same set of fields, or each new calculation uses superset of field of previous table, use only one table instead of creating new table for every calculation. For example, if you need to sort 20 values, display result and then sort another 30 and display them too, most people would write:
local temporaryTable temporaryTable={} for idx=1, 20 do temporaryTable[idx]=GetSomeDataByIndex(idx) end DisplayResult(table.sort(temporaryTable)) temporaryTable={} -- inefficient for idx=1, 30 do temporaryTable[idx]=GetSomeOtherDataByIndex(idx) end DisplayResults(table.sort(temporaryTable))
This example would create one table, trash it after calculation and create another throw-away table again. Removing extra table constructor in line marked with "-- inefficient" will save you memory. Such optimization would be especially effective inside long loops or frequently called function.
Old:
local function OftenCalledSortingFunction() local temporaryTable={} for idx=1, 20 do temporaryTable[idx]=GetSomeDataByIndex(idx) end return table.sort(temporaryTable) end
Placing call to this in frequently called event or even worse in "OnUpdate" handler is a sure way to fill your memory with garbage fast.
New:
local workingTableForOftenCalledSortingFunction={} -- we can create it once and reuse it because we always sort exactly 20 values local function OftenCalledSortingFunction() for idx=1, 20 do workingTableForOftenCalledSortingFunction[idx]=GetSomeDataByIndex(idx) end return table.sort(workingTableForOftenCalledSortingFunction) end
You can futher maximize savings if some other functions too use table with same size/set of field by sharing this working table between them.
Here's example straight from many addons that create UI dropdowns:
local function MyAddon_InitMenu() local info info = {} info.text = "Settings Button1" info.value = "Some value" info.checked = PlayerSettings["Setting1"] UIDropDownMenu_AddButton(info); info = {}; info.text = "Settings Button2" info.value = "Some other value" info.checked = PlayerSettings["Setting2"] UIDropDownMenu_AddButton(info); -- end of settings buttons info = {}; info.text = "Action Button1" info.value = "Value to act on" info.func = function() DoSomething() end; UIDropDownMenu_AddButton(info); info = {}; info.text = "Action Button2" info.value = "Another value to act on" info.func = function() DoSomethingElse() end; UIDropDownMenu_AddButton(info); end
If you ever written something like that, then your addon contributes 4 tables (in this example) to garbage every time this menu is displayed. And if you've used loops to dynamically generate long menus (listing all characters' profiles, for example) then you've lost even more. For efficient use of memory you can define info once, removing all info = {} lines and adding info.checked = nil string after end of settings buttons, to ensure that checked/unchecked state of last settings button doesn't appears on action buttons. This will reduce your losses to one trashed table per call.
And, just like in previous example, you can use one permanent working table and eliminate garbage generation completely:
-- UIDropDownMenu_AddButton only needs table to conveniently pass arguments by name -- All values are copied to their correct places inside UIDropDownMenu_AddButton -- and it doesn't need table we passed any longer after that. This allows us to -- reuse same table for passing parameters, since we know that reference to this -- table won't be saved anywhere as long as we use same fields and thus overwrite -- previous values automatically without need to clear working table in some way. local workingTableForInitMenu={} local function MyAddon_InitMenu() workingTableForInitMenu.func=nil -- erase value that could be there from last call workingTableForInitMenu.text = "Settings Button1" workingTableForInitMenu.value = "Some value" workingTableForInitMenu.checked = PlayerSettings["Setting1"] UIDropDownMenu_AddButton(workingTableForInitMenu) workingTableForInitMenu.text = "Settings Button2" workingTableForInitMenu.value = "Some other value" workingTableForInitMenu.checked = PlayerSettings["Setting2"] UIDropDownMenu_AddButton(workingTableForInitMenu) -- end of settings buttons workingTableForInitMenu.checked=nil -- erase value that could be there from last settings button workingTableForInitMenu.text = "Action Button1" workingTableForInitMenu.value = "Value to act on" workingTableForInitMenu.func = function() DoSomething() end; UIDropDownMenu_AddButton(workingTableForInitMenu) workingTableForInitMenu.text = "Action Button2" workingTableForInitMenu.value = "Another value to act on" workingTableForInitMenu.func = function() DoSomethingElse() end; UIDropDownMenu_AddButton(workingTableForInitMenu) end
Finally, let me stress once again: this works best when you have table of same size or with same set of fields. As you see from the last example, buttons differ by two fields and function now has to take care of that. Forgetting this while using this approach can produce some very funny and hard to track bugs. And clearing table from previous values can degrade performance if there are many fields to clear on each run. Another thing to be aware of is that unlike UIDropDownMenu_AddButton, some function may store table reference and check values from it at some point in future, hoping that nobody else uses this table. When such function do that in future only to get something else that was initially passed, they tend to mess things up in quite spectacular ways. Check the target function to make sure that it doesn't store passed table.