5.3 KiB
AcBinarySerializer — TODO
Priority legend
- P0 blocker · P1 important · P2 nice-to-have · P3 idea
BIN-T-1: Replace JSON-in-Binary request parameters
Priority: P1 · Type: Refactor · Related: ../XCUT/XCUT_ISSUES.md#xcut-i-1 (canonical), AyCode.Services/docs/SIGNALR/SIGNALR_TODO.md
Migrate client→server request parameters from JSON-in-Binary envelope to direct Binary serialization (matching response path). Coordinated change across client, server, and all consuming projects. Do NOT attempt as side-effect of unrelated work.
Acceptance: SignalPostJsonDataMessage<T> replaced by a SignalPostBinaryDataMessage<T> (or equivalent); no JSON round-trip on the wire for request params; benchmarks confirm no regression.
BIN-T-2: Re-evaluate DiscountProductMapping SGen exclusion
Priority: P3 · Type: Investigation · Related: BINARY_ISSUES.md#bin-i-11
Investigate whether the new int Id shadowing pattern can be handled by SGen (via base-class introspection, property-setter lookup on the base) to eliminate the runtime compiled-expression fallback for this entity class.
BIN-T-3: Generate BinarySerializeTypeMetadata / BinaryDeserializeTypeMetadata at compile time
Priority: P1 · Type: Performance · Related: BINARY_ISSUES.md#bin-i-10
Eliminate the dominant first-call cost (reflection + Expression.Compile in metadata ctor) for SGen types by emitting pre-built metadata from the source generator.
Design outline:
TypeMetadataBase/BinarySerializeTypeMetadata/BinaryDeserializeTypeMetadataget a second constructor that accepts pre-computed values (hashes,MinWriteSize,ComplexPropertyCount, flags,IsIId,IdAccessorType, etc.). No reflection executes in this ctor.- Source generator keeps its existing
s_typeNameHash/s_propertyHashesstatic fields (hot-path access stays static, zero indirection) and passes the same references to the metadata — single source of truth, no duplicate computation. ModuleInitregisters both the writer/reader and the pre-built metadata into aGeneratedMetadataRegistry.GetWrapperSlowconsults this registry first, falling back to the reflection-basedMetadataFactoryfor runtime-only types.- Lazy
RuntimeInit()pattern forExpression.Compileproperty accessors:TypeMetadataBasegetsvolatile bool _runtimeInitialized+internal void RuntimeInit()(idempotent, no lock needed).GetWrapperSlowcallsmetadata.RuntimeInit()only whenwrapper.GeneratedWriter == null || !Options.UseGeneratedCode— SGen types skip it entirely (they never touch runtime accessors on their own metadata; non-SGen child types have their own metadata and run the factory path normally).- Hybrid mode stays correct: an SGen type on the SGen path never uses its own property accessors; a non-SGen child type's metadata runs the reflection ctor as today.
volatileguards the flag; multiple contexts may race intoRuntimeInit, second run is a no-op.
Thread safety: GlobalMetadataCache is ConcurrentDictionary; generated metadata is registered once at ModuleInit; wrapper construction is per-context and unchanged.
Acceptance:
- Cold benchmark: first
Serialize<T>of a fresh SGen type shows no reflection /Expression.Compileon the call stack. - Runtime fallback (
UseGeneratedCode=false) still produces identical wire output and uses the full metadata accessors. - Deserialize side has parity (same approach for
BinaryDeserializeTypeMetadata). - Existing tests pass; wire format unchanged.
BIN-T-4: JIT Tier 1 warmup for generated hot methods
Priority: P2 · Type: Performance · Related: BINARY_ISSUES.md#bin-i-10
After BIN-T-3 lands, JIT of generated WriteProperties / ScanObject / ScanForDuplicates becomes the dominant residual first-call cost for SGen types. Options to evaluate (benchmark before committing):
[MethodImpl(MethodImplOptions.AggressiveOptimization)]on the generated hot methods — skips Tier 0, compiles directly at Tier 1. Simple generator change. Trade-off: larger one-time JIT cost in exchange for eliminating the Tier 0→1 recompile step.- Background prewarm from
ModuleInit:Task.Run(() => RuntimeHelpers.PrepareMethod(handle))for each registered writer/reader method. Parallelizes JIT with app startup. Keep it opt-in (option flag) to avoid surprising consumers with extra startup threads. - ReadyToRun (R2R) in consuming projects' publish config — pre-compiles IL to native at publish time. External to SGen, complementary. Document as a recommended publish setting.
- Code chunking (split generated methods exceeding a property threshold into sub-methods, e.g.
WriteProperties_Part1/_Part2) — measure first. Only beneficial for unusually large types (20+ properties / nested collections). Call overhead can offset gains; JIT inliner may already handle reasonably-sized methods well. - Native AOT — out of scope for this TODO; separate architectural decision with deployment-model implications.
Acceptance:
- Benchmark a realistic entity graph (≥ 3 referenced child types) and show first-call time within ~10% of steady-state after BIN-T-3 + chosen mitigation(s).
- Document which combination is recommended for SignalR hot-path workloads vs. batch serialization.