Versions: CFA 2.3.0, Minecraft 1.21.1, NeoForge 21.1.224, sophisticatedcore 1.4.25, sophisticatedstorage 1.5.38. The same code is in master, so 2.5.0 is affected too.
What happened
Our server dropped to 4 TPS (~252ms per tick). A spark profile pointed at one call chain: a Sophisticated Storage hopper upgrade pushed items into a filtered controller network. Every insert attempt ran FilterLogic.stackMatchesFilter, which your MixinSophItemMatcher hooks.
Profile: https://spark.lucko.me/Sh6X6XsL8y
- 73.5% of the server thread was in that filter chain.
- 46.6% was inside the CFA hook (
sophFilterMatcher -> CFAFilterSelector.getFilterType).
- 33.5% of all server CPU was
HashMap.getNode, called from IntegrationHandler.isModLoaded.
Cause
CFAFilterSelector.getFilterType runs once per stack-filter comparison. Each call:
- Clones the enum array with
FilterType.values().
- Calls
IntegrationHandler.isModLoaded for up to 4 filter types. The method memoizes, but each call still does containsKey + get on a HashMap<String, Boolean>. That is two String-hash lookups per type, per comparison.
A network with large chests makes millions of these comparisons per second, so it ends up using a lot of CPU cycles.
Fix that we tested
Added two caches in CFAFilterSelector:
- Compute the list of loaded filter types once. The mod list cannot change at runtime.
- Cache the
getFilterType result per Item in a ConcurrentHashMap<Item, FilterType>. This is safe because every isSupportedFilterItem in 2.3.0 is a pure instanceof check on stack.getItem().
We patched this into the jar on our server. Heres the difference between the spark profiles:
| Metric |
Before |
After |
| TPS |
4.0 |
20.0 |
| Filter chain CPU |
118,892ms (73.5%) |
40ms |
HashMap.getNode self time |
33.5% |
0.6% |
After profile: https://spark.lucko.me/uPWf4AHhUB
I can open a PR with this change if you want it.
Versions: CFA 2.3.0, Minecraft 1.21.1, NeoForge 21.1.224, sophisticatedcore 1.4.25, sophisticatedstorage 1.5.38. The same code is in master, so 2.5.0 is affected too.
What happened
Our server dropped to 4 TPS (~252ms per tick). A spark profile pointed at one call chain: a Sophisticated Storage hopper upgrade pushed items into a filtered controller network. Every insert attempt ran
FilterLogic.stackMatchesFilter, which yourMixinSophItemMatcherhooks.Profile: https://spark.lucko.me/Sh6X6XsL8y
sophFilterMatcher->CFAFilterSelector.getFilterType).HashMap.getNode, called fromIntegrationHandler.isModLoaded.Cause
CFAFilterSelector.getFilterTyperuns once per stack-filter comparison. Each call:FilterType.values().IntegrationHandler.isModLoadedfor up to 4 filter types. The method memoizes, but each call still doescontainsKey+geton aHashMap<String, Boolean>. That is two String-hash lookups per type, per comparison.A network with large chests makes millions of these comparisons per second, so it ends up using a lot of CPU cycles.
Fix that we tested
Added two caches in
CFAFilterSelector:getFilterTyperesult perItemin aConcurrentHashMap<Item, FilterType>. This is safe because everyisSupportedFilterItemin 2.3.0 is a pureinstanceofcheck onstack.getItem().We patched this into the jar on our server. Heres the difference between the spark profiles:
HashMap.getNodeself timeAfter profile: https://spark.lucko.me/uPWf4AHhUB
I can open a PR with this change if you want it.