From ff7e70258e688ccd2f0f7cbe59137dcb130e8119 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Thu, 13 Aug 2026 21:54:44 -0700 Subject: [PATCH] perf: size the auto-denylist cap to a Dictionary capacity 65,536 sat just past a resize boundary. Dictionary grown from empty steps 36,353 -> 75,431 -> 156,437 (ExpandPrime doubles and takes the next prime), so a 65,536 cap allocated capacity 75,431 and stranded 9,895 slots at 52 bytes each -- 515 KB reserved and unreachable. 156,437 fills a capacity exactly: 2.4x the headroom for ~8 MB, no wasted slots. Measured Entry layout is 48 bytes (4 hashCode + 4 next + 8 alignment padding for UInt128 + 16 key + 8 value) plus a 4-byte bucket int. The binding constraint is the sweep, not memory, since it is O(n) on the loop. Measured full sweeps: ~2 ms at 156,437, ~6 ms at 324,449, ~26 ms at 968,897 -- so a million-entry cap costs 72 MB and a three-tick stall when a flood's entries all lapse together. The remark records the curve because maxEntries is operator-facing. Co-Authored-By: Claude Opus 5 (1M context) --- .../Network/AutoDenylist/AutoDenylistConfiguration.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Projects/UOContent/Network/AutoDenylist/AutoDenylistConfiguration.cs b/Projects/UOContent/Network/AutoDenylist/AutoDenylistConfiguration.cs index fd29bb0c2..3af1a300b 100644 --- a/Projects/UOContent/Network/AutoDenylist/AutoDenylistConfiguration.cs +++ b/Projects/UOContent/Network/AutoDenylist/AutoDenylistConfiguration.cs @@ -75,6 +75,12 @@ public record AutoDenylistSettings /// stops it becoming the exhaustion it prevents. At the cap new addresses are not tracked, but are still /// disconnected by whichever gate detected them. /// + /// + /// A Dictionary capacity, not a round number. Growing from empty it steps 36,353 → 75,431 → + /// 156,437, so this fills one exactly instead of stranding slots: 65,536 sat just past a resize and left + /// 9,895 of them (52 bytes each) unusable. Costs ~8 MB. Raising it is paid on the loop, where a full + /// sweep runs ~2 ms here, ~6 ms at 324,449 and ~26 ms at 968,897. + /// [JsonPropertyName("maxEntries")] - public int MaxEntries { get; set; } = 65536; + public int MaxEntries { get; set; } = 156_437; }