Third rule of Optimisation

The first rule of optimisation is “Don’t”.

But we at CC2maps.com aren’t like normal people, so we’ve done it anyway.

The second rule (in my head) is “measure”. Followed by “don’t do stuff you don’t need to do – eg, repeating things”

Hot on the heels of my earlier post More optimization! I had a thought.

Quite a lot of what goes on in the vehicle control screen involves iterating through all the vehicles on the map, deciding to display them or not, calculating distances to other units etc.

There is a fairly common pattern repeated in the lua code in a lot of scripts:

        local vehicle_count = update_get_map_vehicle_count()
        for i = 0, vehicle_count - 1, 1 do
            local vehicle = update_get_map_vehicle_by_index(i)
            if vehicle:get() then
                -- do something with vehicle
            end
        end

This is actually pretty fast already, but it still is done several times. I think even unmodded this is called 3-4 times per update(). With revolution this grows to 5 and occasionally 7 times.

All this calling into native code to get the vehicle objects, which I suspect gets a fresh object every time. We could save time here if we didn’t do this over and over.

My earlier attempt at memoization met with mixed results, partly because I was using random access to a table to check if my values were cached, table indexing is quite expensive (due to computing hashes). But I don’t need that here, I just need to get the whole table of vehicle objects once per frame rather than 4-6 times per frame.

So I now have this:

g_vt = {}
g_vt_tick = 0
function get_vehicles_table()
    local now = update_get_logic_tick()
    if now > g_vt_tick then
        g_vt = nil
    end

    if g_vt == nil then
        g_vt_tick = now
        g_vt = {}
        local vt = g_vt
        local vehicle_count = update_get_map_vehicle_count()

        for i = 0, vehicle_count - 1, 1 do
            local vehicle = update_get_map_vehicle_by_index(i)

            if vehicle:get() then
                table.insert(vt, vehicle)
            end
        end
    end

    return g_vt;
end

Now, If I just want to iterate all the vehicles, I can do:

for x, vehicle in pairs(get_vehicles_table()) do
    local vehicle_team = vehicle:get_team()
    local vehicle_attached_parent_id = vehicle:get_attached_parent_id()
    <etc>

The first time I call get_vehicles_table() this tick, it creates a fresh table, and then we just iterate it using pairs, we don’t care about the order so this is very fast.

Lets see the timings!

timer armed
------- > 
start timer	3	1748102164	8586
done timer	1748102184
calls	9845
calls/sec	492.25
timer armed
------- > 
start timer	3	1748102203	9137
done timer	1748102223
calls	9694
calls/sec	484.7

Woo! 484+ calls/sec! At the end of my previous post, without this change we were at about 280 calls/sec.

So this is significant!!!

Also included in this is using table.insert(fow_visible, bool) to store the island fog of war data rather than table[id] = bool. That bought me another 15 calls/sec.

Lua direct table access (eg, associative array access) seems a lot slower than simply iterating the whole thing. I guess there probably comes a point where indexer access is faster than iteration where there is a lot of data.

We shall see!

By:

Posted in:


One response to “Third rule of Optimisation”

Leave a reply to Even More Optimal! – CC2Maps.com Cancel reply