sullerandras / UtilizationMonitor

UtilizationMonitor: Factorio mod to see if factories are fully utilized
MIT License
0 stars 6 forks source link

Horrible FPS/UPS impact in lategame #3

Closed ST-DDT closed 6 years ago

ST-DDT commented 6 years ago

In one of my very large factories (>2k copper cable/second) this mod pushes me from ~60FPS to ~18FPS. The timing monitor shows that the mod takes roughly 35ms(?) to complete. All my other mods together only take ~2ms.

I really like your mod. Do you see a possibility to somehow improve the mod's performance? I would be fine with reducing the accuracy by only processing the factories every 10 or 100 ticks, as long as the game remains playable.

Thanks for your mod and happy new year.

sullerandras commented 6 years ago

I will try to test it with many factories to see if there's anything i can do to make it faster. It was really not optimized for performance, i just hacked it together.

First i need find out what is slow: the calculation, displaying the percentages or that it allocates too much memory. I think the calculation but better to make sure before optimizing the wrong thing.

My initial ideas:

sullerandras commented 6 years ago

@ST-DDT Improved the performance a little bit, check out v0.2.5. Let me know if it's any better for you.

ST-DDT commented 6 years ago

Nice. The time needed for the mods was reduced to 28-30ms. Unfortunately this is still too slow for my map, as it limits the FPS to 20-21.

If I should name a number for acceptable time it can take I would go for 5-10ms. Which would allow me to play with almost 60 FPS.

I will try to improve the performance some more this weekend as well. If you want me to test more builds, just upload them and will try them out.

sullerandras commented 6 years ago

@ST-DDT I started working on a way to limit the number of entities we can update in every tick. If you could test it that would be great: https://github.com/sullerandras/UtilizationMonitor/tree/improve-performance Note that it's work in progress so some things are not supported (e.g. moving an entity will not move the label).

There is a new mod setting to change the number of entities to update per tick. Copied the idea from the bottleneck mod: https://mods.factorio.com/mods/trold/Bottleneck

ST-DDT commented 6 years ago

The new system has a lot better performance. With the default settings of 1000u/t it raises the performance to 45 tps/ups. Which is at a playable value. The mod then takes ~5 (ms?) for its processing.

With a modified settings of 100u/t it raises the performance to 52 tps/ups. Which is at a good playable value. The mod then takes ~0.5 (ms?) for its processing. The 0.5 is hardly any slower than the game without the mod, thats why I don't understand why it still costs me up to 8 fps/ups. (Mathematically it should only take ~2.8 FPS). 100u/t -> factory stats are updated once per second (for me) => a complete loop is completed after a one minute, which in fact gives a good average, if the supply is sometimes limited.


Here are some suggestions though:


function update_entity(data, id, tick, time_strech)
  local is_working = (is_working(data, id) and 1 or 0)
--  if data.sec_avg.add == nil then -- after loading the save game the add method is not defined :(
--    global.need_reset = true
--    return
--  end
--  for i = 1, time_strech do // Don't run the count multiple times, as it basically just erases some precious history data, without helping in precision.
--              // it does not matter if the percentage is a few seconds old.
    data.sec_avg:add(is_working)
--  end
--  if tick % 60 < time_strech then // only update after a full loop, 
  if data.sec_avg.next_index == 0 then
-- maybe use some kind of initial index per entity that spreads the label updates more evenly.
--    data.min_avg:add(data.sec_avg:avg()) // Remove this entirely and just use the average directly
--                         // or is there a special reason for this variant?
--                         // this is no longer as precise as a second/minute anyway
    --[[
    game.print("last sec avg: " .. (data.sec_avg:avg()*100) .. "%, last min avg: " .. (data.min_avg:avg()*100))
    --]]
--    if global.show_labels then // Move this to on_tick
      local percent = math.max(math.floor(data.min_avg:avg()*100), 0)
--      if data.label == nil then // validate this on creation/load/reset
--        data.label = data.entity.surface.create_entity{name = "statictext", position = --label_position_for(data.entity), text = tostring(percent).."%"}
--      else
        data.label.text = percent .. "%"
--      end
    end
  end
end
sullerandras commented 6 years ago

Thanks for you input. Indeed 0.5 ms should not make it 8 fps slower. How many entities you have? I tested with 10 thousand entities.

The initial idea was to give the user the average utilization of the machines for the last minute. That's 60 seconds, each of them has 60 ticks. I did the averaging in two layers, the first layer averages the utilization for the last second (this is the sec_avg), the second layer (min_avg) averages the last 60 values of the first layer, so it's an average of averages for the last 60 seconds. Changing it to only one layer would give you the utilization for the last second when there are only a few buildings, which would be completely useless. Bottleneck mod already gives you that information (that the machine works or not at this moment). So i think we cannot simplify that part, since the mod should work with both small and big factories. And even if we could, calculating the second layer wasn't slow in my testing.

I added time_stretch (lol it has a typo) to make sure that the average we use is not too old, so some old data will not skew the percentage too much. Not sure how much difference it makes, but a simple for loop shouldn't be too slow.

sullerandras commented 6 years ago

@ST-DDT Refactored the code a bit, for me it's about 10% faster then before.

Also tested with 10k mining drills what takes long in the update_entities function, here's what i found:

Any idea how to make is_working or add faster?

ST-DDT commented 6 years ago

Mhh, how fast is the constant map/list/table access when you do global.x[id] maybe that can be simplified to a single data = global.datas[x] where all data are stored, that are needed for the mod?

EDIT: Sorry looked at the wrong branch. You actually did that already.

Is working could also be simplified as a method reference inside that data.

I will try that and then report back.

ST-DDT commented 6 years ago

Here my result: https://github.com/sullerandras/UtilizationMonitor/compare/improve-performance...ST-DDT:improve-performance2

It roughly halves the time taken.

For whatever reason it won't go to 100% only 99%

With 9984 factories: 100u/t won't cause performance impact, on the downside you should decrease the iterations per update as well to guarantee somehow fast updates especially for new factories.

sullerandras commented 6 years ago

Your changes look very good, thanks a lot. You should create a pull request in github. I'll test it when i have some time and probably merge it in straight away. So you tried your changes in a real factory which is big? Does the reduced accuracy still make my mod useful? I mean if an assembling machine is checked only once every few seconds, then i think the utilization percentage would be way more inaccurate to be useful, so i'd guess that the "Bottleneck" mod would be better in that case. Any opinion on this?

Unrelated to the performance improvements we are doing here, but i'm considering not using global at all. That way the savegames would not be inflated in size, which would make save/load a lot faster for big factories. The only downside is that the calculation would have to start from scratch every time when loading a savegame. I think that's a reasonable tradeoff, i usually play for hours when i start playing, so wouldn't affect much. What do you think?

sullerandras commented 6 years ago

Also please add a new comment instead of editing the previous comment, because Github doesn't sent email notifications for edits.

ST-DDT commented 6 years ago

So you tried your changes in a real factory which is big?

It consists of almost 10k buildings that are tracked by UM.

Does the reduced accuracy still make my mod useful? I mean if an assembling machine is checked only once every few seconds, then i think the utilization percentage would be way more inaccurate to be useful, so i'd guess that the "Bottleneck" mod would be better in that case. Any opinion on this?

Your mod is still useful. The data will be collected using a random tick thats why the accuracy will be sufficient. It will miss working and idle ticks randomly. Which balances over time. The main advantage of your mod is that it shows long term data (are enough materials delivered) whereas bottleneck only shows live data (did i place all inserters).

Unrelated to the performance improvements we are doing here, but i'm considering not using global at all.

Mhh. I don't know. It is sure possible but we have to make sure that in my version the long term data are properly initialized. Set update to true for the first 60 iterations or something like that. Also we have to somehow track the labels to avoid creating them more than once.

On the other hand. The label is probably the largest data impact so i don't know how useful discarding other data might be.

sullerandras commented 6 years ago

@ST-DDT I merged in you PR and made some minor improvements. See https://github.com/sullerandras/UtilizationMonitor/pull/7

The savegames are zip files so you can read their content and as i see the data is stored in script.dat (inside the zip). The majority of the content is the sec_avg and min_avg. The sec_avg only stores ones and zeroes since is_working is either 1 or 0, so it could be a single 64 bit integer but LUA 5.2 only has floats :(

ST-DDT commented 6 years ago

It still has bitwise operations https://www.lua.org/manual/5.2/manual.html#6.7 So even if we don't have 64 bit variables we can still use two 32bit ones. But we can only use it for sec_avg and not for min_avg, because that uses floats all over.

Col-Wardeth commented 6 years ago

Hello. Can I suggest a feature? Would it be hard to implement a pause in the mod's main loop controlled by a hotkey? So you can put out your machines, feed them with resources, turn the mod on, check for utilization percents and then turn it off to conserve FPS/UPS.

ST-DDT commented 6 years ago

With the latest changes it does no longer have that much impact on the performance. But i guess this would also be an option. Maybe we should finish the performance updates itself first.

The performance data are shared on the server, any suggestions how to handle this hotkey handling on multiplayer?

Col-Wardeth commented 6 years ago

If one player turns off his local calculations, would it hurt other players (they would have more to calculate) or not? If yes, then maybe only allow to pause the mod in singleplayer?

ST-DDT commented 6 years ago

There is no such thing as turning the local calculations of. Either nobody or everybody has to do the calculations, unless you know a way to mark entities as per player/local.

Col-Wardeth commented 6 years ago

Well, it still leaves us with such an option for singleplayer.