Repository navigation
Reuse one XmlSerializer for profile load and save (~100 ms → ~1 ms per profile switch) - #134
Merged
Merged
Conversation
XmlSerializer built with XmlAttributeOverrides is not cached by .NET. Every profile load (TryPrepare) and save (SaveProfileNew) built a new one, which generated and loaded a new dynamic assembly (~100 ms with its first use) that never unloads. Temporary profile switches pay this on every press and every release. Share one lazily built, thread-safe instance (ProfileDTO.Serializer). Warm TryPrepare: ~101 ms -> ~0.9 ms; loaded assemblies +1 per call -> +0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
PreparedProfileLoad.TryPrepareandSaveProfileNeweach built a newXmlSerializer(typeof(ProfileDTO), ProfileDTO.GetAttributeOverrides()). .NET only caches serializers built from(Type)or(Type, string). WithXmlAttributeOverrides, every construction generates and loads a new dynamic assembly, which never unloads. The constructor plus the firstDeserializeon each new instance cost about 100 ms.Temporary profile switches (for example a hold-to-switch special action with automatic untrigger) run
TryPrepareon every press and every release. Each press therefore cost about 100 ms of CPU and leaked an assembly, which can cause stutter in games and delays the switch on quick taps.Change: one shared, lazily built instance,
ProfileDTO.Serializer(Lazy<XmlSerializer>,ExecutionAndPublication), used by both call sites.SerializeandDeserializeare thread-safe, and no call site attachesUnknown*event handlers, so sharing one instance is safe. No other production code builds a serializer with overrides.Results (a local timing harness, not included in this PR; ~17.8 KB profile, 5 warm-up calls and 40 timed calls, before and after measured in the same session on the same machine):
TryPreparewall / CPUThe first profile load or save in a process still pays the one-time ~100 ms.
On hardware (DualSense Edge, hold-L2-to-switch profile action): DS4Windows CPU during rapid tapping dropped from 3–5% to ~1.5%. The log still shows one profile line per press.
Tests:
ProfileSerializerCacheTests: the same instance is returned each time, and 20 repeatedTryPreparecalls leave the assembly count unchanged.main).