Avoid delegate dispatch in DetectBuildingAuth - #75
Open
dexsper wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DetectBuildingAuthusedBaseEntity.Query.Server.GetInSphereFast(pos, radius, buffer, Func<BaseEntity,bool> filter), which runs the supplied delegate against every candidate entity the spatial grid returns before the caller can filter/cast. Replaced it with the generic, type-filtered overloadGetInSphere<BuildingPrivlidge>(pos, radius, list, DistanceCheckType.None), which filters by type inside the grid query itself, instead of returning allBaseEntitycandidates and filtering afterwards via a delegate.See
plugins/rust/RustApp.cs,DetectBuildingAuth(~line 4725). Key changes:The fixed-size
_buildAuthArr/_buildAuthFilterbuffer +Array.Clearcleanup were replaced with a pooledList<BuildingPrivlidge>(Pool.Get/Pool.FreeUnmanaged), matching the pooling convention already used elsewhere inRustApp.cs. No other behavioral change: same radius, samesqrMagnitudecheck, same early-return-on-first-match logic.Why
GetInSphere<T>and notGetInSphereFastGetInSphereFastnarrows candidates with aFunc<BaseEntity, bool>delegate that is invoked once per entity the grid cell returns, regardless of type. Each candidate requires a delegate invocation, adding an indirect managed call on top of theis BuildingPrivlidgecheck it wraps. The generic overload does the type check insideGrid<T>.Query, so there is no per-candidate delegate call. It also does not walk everyBaseEntityin range: onlyBuildingPrivlidge.This matches an earlier local call-stack capture (23 calls in one window): the delegate
<>c::<.cctor>b__178_0(the invocation itself plus theis BuildingPrivlidgecheck it wraps; the profiler can't separate the two, both are the lambda's self-time) was 0.718ms of the 0.942ms inclusive time inDetectBuildingAuth.Verification
Built a standalone benchmark plugin (
RustAppBench.cs, not part of this PR) exposing both implementations behind chat commands, driven 5000 iterations each, wrapped inServerProfiler.RecordScopeto get an exact call-tree viaprofile.perfsnapshot.Aggregated over 5000 calls (Main Thread). Snapshot
tsis microseconds (ServerProfiler.TimestampToMicros):GetInSphereFast(before)GetInSphere<T>(after)Grid<T>::Querytotal (self + lambda)is BuildingPrivlidge, 455000 calls, 91 per call)DetectBuildingAuthinclusive, 5000 callsDetectBuildingAuthinclusive per call (avg)Raw snapshot lines (call 100 of 5000, exported
.jsontrace, oneBuildingPrivlidgein range):Before (
GetInSphereFast): one call from ts 599 to ts 604 (5 µs), filter lambda entered/exited 91 times:{"name": "a21g2bi2.adl!RustAppBench::DetectBuildingAuth_Original", "ph": "B", "ts": 599, "tid": 1} {"name": "Assembly-CSharp!EntityTree::GetInSphereFast", "ph": "B", "ts": 599, "tid": 1} {"name": "Facepunch.System!Grid`1::Query", "ph": "B", "ts": 599, "tid": 1} {"name": "a21g2bi2.adl!<>c::<.cctor>b__17_0", "ph": "B", "ts": 599, "tid": 1} {"ph": "E", "ts": 599, "tid": 1} {"name": "a21g2bi2.adl!<>c::<.cctor>b__17_0", "ph": "B", "ts": 599, "tid": 1} {"ph": "E", "ts": 599, "tid": 1} ... (repeats 91 times per call, one B/E pair per grid candidate) ... {"ph": "E", "ts": 603, "tid": 1} {"ph": "E", "ts": 603, "tid": 1}After (
GetInSphere<BuildingPrivlidge>): no lambda frames, call from ts 298 to ts 301 (3 µs):{"name": "a21g2bi2.adl!RustAppBench::DetectBuildingAuth_Optimized", "ph": "B", "ts": 298, "tid": 1} {"name": "Assembly-CSharp!EntityTree::GetInSphere", "ph": "B", "ts": 298, "tid": 1} {"name": "Facepunch.System!Grid`1::Query", "ph": "B", "ts": 298, "tid": 1} {"ph": "E", "ts": 300, "tid": 1} {"ph": "E", "ts": 301, "tid": 1} {"name": "Assembly-CSharp!BuildingPrivlidge::IsAuthed", "ph": "B", "ts": 301, "tid": 1} {"ph": "E", "ts": 301, "tid": 1} {"ph": "E", "ts": 301, "tid": 1}Pooling (
Pool.Get<List<BuildingPrivlidge>>()/Pool.FreeUnmanaged) follows the same convention already used everywhere else inRustApp.cs(15+ call sites), so no new allocation pattern is introduced.