Tip: Cache your constant tables

Place to post guides, observations, things related to modding that are not mods themselves.
Post Reply
User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3700
Joined: Tue May 13, 2014 11:06 am
Contact:

Tip: Cache your constant tables

Post by DaveMcW »

I got a 10% speed boost in Recursive Blueprints by replacing this:

Code: Select all

function get_construction_signal(network)
  return network.get_signal({type="item", name="construction-robot"})
end
... with this:

Code: Select all

local CONSTRUCTION_SIGNAL = {type="item", name="construction-robot"}
function get_construction_signal(network)
  return network.get_signal(CONSTRUCTION_SIGNAL)
end
The one drawback is that lua does not enforce cached tables being constant, so you can break things by accidentally modifying the table. This is not a big problem in control.lua, but it might be an issue in data.lua.

User avatar
QGamer
Fast Inserter
Fast Inserter
Posts: 213
Joined: Fri Apr 14, 2017 9:27 pm
Contact:

Re: Tip: Cache your constant tables

Post by QGamer »

(Sorry, I'm not super experienced with Lua.)
By cache you mean put them as local variables in Control.lua, right?
What types of values should I cache? Just tables, or would it help to cache other types as well?
"Adam fell that men might be; and men are, that they might have joy."

quyxkh
Smart Inserter
Smart Inserter
Posts: 1028
Joined: Sun May 08, 2016 9:01 am
Contact:

Re: Tip: Cache your constant tables

Post by quyxkh »

From `utils/consolegoodies.lua` in my own utilities library, implementing this:

Code: Select all


local foundsigs={}
function sig(id,c)
    -- return a spec for the given signal id and (optional) count. The
    -- signal type is found in prototypes, omitted count returns just
    -- the id, so
    --     sig'signal-black' → {name='signal-black',type='virtual'}
    -- and   sig('coal',100} → {count=100,signal=sig'coal'}  (further expanded).
    -- for extra fun, the `signal-` prefix is made optional, so
    --     sig'W' → {name='signal-W',type='virtual'}

    if c then return {count=c,signal=sig(id)} end
    if not foundsigs[id] then
	foundsigs[id] = game.virtual_signal_prototypes[id] and	{name=id,type='virtual'}
		     or game.item_prototypes[id] and  {name=id,type='item'}
		     or game.fluid_prototypes[id] and  {name=id,type='fluid'}
		     or game.virtual_signal_prototypes['signal-'..id]
					    and  {name='signal-'..id,type='virtual'}
		     or {name='**unknown signal id "'..id..'"'}
	local _, _2, short = foundsigs[id].name:find'^signal%-(.*)'
	if _ then
	    foundsigs[foundsigs[id].name] = foundsigs[id] -- with signal-
	    foundsigs[short]              = foundsigs[id] -- and without it
	    end
        end
    return foundsigs[id]
    end
edit: @QGamer, literal tables cause allocation when they're encountered, it's cheap but not nearly as cheap as not doing it, keeping previously-used tables around if they'll serve as well. So I'll do things like `local empty = {}`. Numbers and strings don't cause allocation, tables and functions do, that much I'm sure of.

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: Tip: Cache your constant tables

Post by eduran »

quyxkh wrote:
Sat Feb 02, 2019 6:17 pm
So I'll do things like `local empty = {}`.
Can you elaborate when and how this would be useful?

quyxkh
Smart Inserter
Smart Inserter
Posts: 1028
Joined: Sun May 08, 2016 9:01 am
Contact:

Re: Tip: Cache your constant tables

Post by quyxkh »

eduran wrote:
Sat Feb 02, 2019 6:45 pm
quyxkh wrote:
Sat Feb 02, 2019 6:17 pm
So I'll do things like `local empty = {}`.
Can you elaborate when and how this would be useful?
Sure: any time your code passes through a `{` lua makes a new table. If instead of writing `{}` for an empty table you say `local empty = {}` and use `empty`, it's faster—by, as DaveMcW's example shows, more than just a little. So for instance search functions will often return `nil` if they don't find anything, but you can't index `nil`. The usual code taught to beginners to deal with that is bulky and repetitive, boilerplate. Instead of a big separate if statement it's often better to say e.g. `for k,v in pairs(train.get_fluid_contents() or empty)` or `(s.find_entity('rail-signal',pos) or empty).direction` or the like. My consolegoodies code puts a metatable on its empty so I don't unthinkingly stomp on it, `local empty = setmetatable({},{__newindex = function() error 'assigning to empty' end}), I don't usually bother with that in loaded code though.
`

With some nontrivial discipline caching like this even lets you start comparing tables by identity not value, if (using the above code) your main namespace has e.g. `sigW=sig'W'` in it and everybody's going through `sig` for their mappings, you can say `if insig==sigW` for the kind of speed boost that can snowball if you work at it. But this is the kind of thing that can and will bite the unwary, causing endless pain and damage to the unwise and their victims, so heed the warning.

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2915
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: Tip: Cache your constant tables

Post by Optera »

Caching tables only works if they are either read only or used in only one persistent instance, like quyxkh's example with foundsigs.
Having an empty table speed up initializing tables will not work in many cases as lua always references tables.

Code: Select all

local empty = {}

function doSomething()
  local a = empty
  a[1] = "whatever"
  local b = empty
  log (serpent.block(b) ) => {[1] = "whatever"}
end

Post Reply

Return to “Modding discussion”