Construction Signaler


Send construction ghosts to your circuit network.

Content
5 years ago
0.16
5
Circuit network

i Performance improvements

5 years ago
(updated 5 years ago)

You can improve performance by a huge margin if you reduce looping using lookup tables and dictionaries wherever possible.
lua also has caveats where recreating tables is quite expensive, so create them once globally and reuse them by wiping them with table = {} instead of recreating.

e.g. Duplicate finding can be speed up easily by more than 100% through a dictionary with unit_number as key is used.
scanning 600+ ghosts with these changes made update time drop from ~0.5 to >0.1 on my pc

local found = {}
local found_dict = {}

function find_ghosts(network)
  found = {}
  found_dict = {}

  if not network then
    return found
  end

  for _,cell in pairs(network.cells) do
    local pos = cell.owner.position
    local r = cell.construction_radius
    local bounds = { { pos.x - r, pos.y - r, }, { pos.x + r, pos.y + r } }
    local ghosts = cell.owner.surface.find_entities_filtered{area=bounds, type="entity-ghost", force=network.force}
    for _, ghost in pairs(ghosts) do
      -- bounds can overlap, so we need to dedup here to avoid requesting too much      
      if not found_dict[ghost.unit_number] then
        found_dict[ghost.unit_number] = true
        found[#found+1] = ghost
      end
    end
  end

  return found
end
5 years ago

Hi Optera, thanks for the pointers. I agree that using a dict to create a "set" rather than looping through would be a big improvement when there's lots of ghosts to dedup. I could do a lot to cache results too, and not re-assigning the signals when they haven't changed would help a lot too.

I think you must be mistaken about wiping tables with table = {} though, that is definitely causing lua to create a new table, same as if it had just been defined, unless Wube has done something very weird to the lua that they've embedded in Factorio (I have a lot of Lua experience, but only started looking at modding Factorio yesterday). Where did you read about that technique?

5 years ago

here to get the response about the wiping tables :)

5 years ago

If you define tables outside a function or even loops it won't create multiple objects in memory and instead reuse the same object.

I have not dug into how lua garbage collector or compiler works different from c# or java, but I highly doubt lua's implementations recognize a table created inside a loop as being the same object.
So reusing globally defined tables will reduce memory usage and also save some time for garbage collector.

5 years ago

I think I must be misunderstanding you, because I read that as the opposite of what you just said in your original post.

"create them once globally and reuse them by wiping them with table = {} instead of recreating " vs " I highly doubt lua's implementations recognize a table created inside a loop as being the same object"

In any case, what I'm saying is there's not going to be any measurable difference between doing:

local found = {}

function find_ghosts()
  found = {}

and

function find_ghosts()
  local found = {}

because either way, you're just creating a new table and assigning it to a local var found each time the function is called. There's no special functionality in lua where saying found = {} re-uses but empties the found table, it just creates a new table and assigns it to found, and the previous table will be GC'd. Being local to the module vs the function makes no difference.

5 years ago

Actually, your mention of c# and java makes me realize where the confusion might be -- those languages allow overloading of the assignment operator, so it may be a valid optimization in those languages, since you're calling the overloaded assignment method on their dict equivalent classes, and it'll just clear out all existing keys without creating a brand-new dict. Lua doesn't have anything like that though, variable assignment is always just changing what object a variable points at.

5 years ago
(updated 5 years ago)

Well in the first example you are reusing the same object each time the function is called and the object is never destroyed by garbage collection.
In the 2nd example a new object is created each time the function is called.

Might be insignificant but it can quickly accumulate when functions are called a few hundred times per tick.

Edit: I just benchmarked global and local declared found and found_dict version over 10k ticks and surprisingly the local version was 0.004 ms/tick faster.
Seems like I was wrong about recycling global tables in lua.

5 years ago

Yeah, that's what I was trying to tell you :)

No worries though, thanks again for taking the time to send me pointers.

5 years ago
(updated 5 years ago)

so....which is the correct way to do it? lol. it's this way, right?:

local found = {}
function find_ghosts()
found = {}
<do stuff>
end function

5 years ago

No, that doesn't really hurt anything but doesn't buy you anything either. Just do the natural thing:

function find_ghosts()
  local found = {}
  <...>
end

New response