Skip to content

Best practices for performance

Matthew edited this page Dec 6, 2018 · 2 revisions

We can get a lot of quick wins performance wise in our code-base by following the rules on https://springrts.com/wiki/Lua_Performance. Please refer to this document when reading the suggestions below

TEST 1 This is about localizing references. By default everything is reached from the global scope and often from a table. Example:

-- slow, avoid
math.min()

-- top of the file
local min = math.min

-- somewhere in your code
min(a, b)

TEST 4 This refers to the lua function math.max. Accepts the highest value of 2. It's faster to manually check which is higher and assign that, compared to the function call. It doesn't matter for code that's ran everyone once in a while, but if it's performance critical code (hot path, often executed), it's wise to apply this optimization.

TEST 8 This test is to show that function definitions inside a loop, or scope, executed many times, costs a lot of overhead. Whenever possible, move your function definition someplace where defining it is only done once.

-- slow
local function test()
    local testf = function () end
    -- or even
    for i = 1, 100 do
        (function())
    end
end

-- instead do
local testf = function () end

local function test()
    for i = 1, 100 do
        testf()
    end
end

TEST 9 This is test is really important for sequential tables (so starting at 1 and always adding 1 to the index). When iterating over the table, calling pairs or ipairs is really slow compared to keeping track of the table size and accessing it +1 when iterating. This only works if the table is sequential!

-- slow
for j,v in ipairs(a) do
  x=v
end

-- fast
for i=1,#a do
  x=a[i]
end

TEST 11 This is a generic solution for pretty much anything. Whenever you have to access a certain key multiple times, reference it in a local. In case of tabels, it's always a reference, changing the reference, changes the original as well, it's not a copy.

local create_entity = game.surfaces.nauvis.create_entity

for i = 1, 100 do
    create_entity(...)
end

-- and
local my_table = {
    some_other_table = {
        counter = 0,
    }
}

-- slow
my_table.some_other_table.counter = my_table.some_other_table.counter  + 1
my_table.some_other_table.counter = my_table.some_other_table.counter  + 1

-- faster
local some_other_table = my_table.some_other_table
local counter = some_other_table.counter
counter = counter + 1
counter = counter + 1
some_other_table.counter = counter

TEST 12: table insert When inserting items into a table, it's common to use table.insert, but this is extremely slow. If you're in a loop, keep a counter and directly set table entries with it.

local current_counter = #my_table + 1
for i = 1,1000000 do
    my_table[current_counter] = i
    current_counter = current_counter + 1
end

If you want to append an item to a table without a loop, use: my_table[#my_table + 1]

TEST 12: table create When creating a table, lua does some smart memory allocating as it knows ahead of time what the size should be. It's wise to avoid using custom indexes, but it's also important to combine the creation of the array and setting the values. If you know ahead of time what the size will be and this is static, reserve some slots.

-- slow
local my_table = {}
my_table[1] = 'foo'
my_table[2] = 'bar'
my_table[3] = 'baz'

-- faster
local my_table = {true, true, true}
my_table[1] = 'foo'
my_table[2] = 'bar'
my_table[3] = 'baz'

-- fastest
local my_table = {'foo', 'bar', 'baz'}

TEST 13 Lua doesn't do well with static analyzation. Whenever it sees a table structure, it will create and insert that table structure. Meaning if you have a table definition inside a loop or function callback, it will create, initialize, and store this table in memory every single iteration. This also triggers heavy garbage collection. Note that this is only useful for static code, something that is not altered, like a color definition in factorio: {r = 255, g = 200, b = 100}. A side-effect of this is that the table is never copied, it's always a reference, so make sure it's not modified.

-- slow
local my_table = {}
for i = 1, 100 do
    my_table[i] = {r = 255, g = 200, b = 100} -- created and stored in memory, then garbage collected
end

-- fast
local my_table = {}
local color = {r = 255, g = 200, b = 100}
for i = 1, 100 do
    my_table[i] = color
end
Clone this wiki locally