Skip to content

feat: perf improvements #minor - #79

Merged
Shiqan merged 11 commits into
masterfrom
perf-improvements
Sep 13, 2026
Merged

Shiqan merged 11 commits into
masterfrom
perf-improvements

Conversation

@Shiqan

@Shiqan Shiqan commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner

Performance & Memory Allocation Improvements

This PR optimizes the IO, memory slicing, and bitstream parsing hot paths in the replay reader. By replacing reflection and allocating operations with Span<byte>, ReadOnlyMemory<byte>, BinaryPrimitives, zero-copy buffer slicing, and reusable object pools, end-to-end replay parsing is significantly faster and allocates ~34% to 38% less managed heap memory.


Benchmark & Real-World Results

1. Recent Replay Comparison 16.03 MB)

Metric master (Baseline) perf-improvements Improvement
Heap Allocations 212.85 MB 140.32 MB -72.53 MB (-34.1%)
Cold Parse Time 622 ms 462 ms 25.7% faster
Warm Parse Time 440 ms 404 ms 8.2% faster
Average Parse Time 555 ms 459 ms 17.3% faster
GC Gen 0 Pauses 10–11 collections 5 collections -50% GC pressure

Summary of Changes

1. Unreal.Core (IO & Bitstream)

  • Direct Span Primitives: Replaced BitConverter and sliced allocations with BinaryPrimitives.Read*LittleEndian directly over Bytes.Span.
  • Zero-Alloc Enum Deserialization: Replaced Enum.ToObject(typeof(T), byte) with Unsafe.As<byte, T> and Unsafe.As<uint, T>, eliminating boxing and reflection.
  • BitReader 64-Bit Word Shifting: Replaced single-bit loops in ReadBitsToInt and ReadBitsToLong with unaligned 64-bit word reads (BinaryPrimitives.ReadUInt64LittleEndian) and bitwise shifts (7.3x faster).
  • Zero-Alloc Property Reading: Added FillBitsFrom to deserialize property bunches directly into internal reusable buffers instead of allocating a new byte[] for every property.
  • Flat Array TempEnd: Replaced Dictionary<FBitArchiveEndIndex, int> with a fixed int[4] array for bunch temp ends (4.6x faster).
  • String Prefix Stripping: Replaced LINQ .Where(...).Any() in RemoveAllPathPrefixes with an allocation-free ReadOnlySpan<string> loop (-95.4% allocations).
  • Zero-Copy Memory Slicing: Added ReadMemory(byteCount) in FArchive and BinaryReader to slice underlying buffers without copying.

2. Unreal.Core (Replication & Guidance)

  • Zero-Copy Raw Packets: Introduced ReceivedRawPacket(ReadOnlyMemory<byte>), eliminating hundreds of thousands of packet array allocations per replay.
  • Bunch Reuse: Reused _currentBunch for non-partial bunches, saving ~350,000 bunch object allocations per parse.
  • Lazy Vector Serialization: ConditionallySerializeQuantizedVector now avoids instantiating default new FVector() instances when no delta was serialized (-50% vector allocations).
  • Direct NetGuid Cache Resolution: Added direct 1-step index lookup (_netFieldExportGroupFromIndex) to bypass 2-step dictionary lookups (2.1x faster).

3. FortniteReplayReader

  • Modernized Chunk Decryption: Used File.ReadAllBytes with zero-copy chunk slices and in-place Aes.DecryptEcb over spans.

4. BenchmarkReader & Testing

  • Modernized Benchmark Harness: Simplified Program.cs to top-level statements delegating to BenchmarkDotNet.Running.BenchmarkSwitcher. Allows interactively selecting any benchmark or passing CLI filters (e.g., -- 4 or --filter *BenchmarkEnumParsing*).
  • Benchmark Suite: Added isolated BenchmarkDotNet classes for each distinct optimization.
  • Regression Safety Net: Added xUnit performance and allocation tests in Unreal.Core.Test asserting functional parity and zero heap allocations (GC.GetAllocatedBytesForCurrentThread() == 0).

@Shiqan
Shiqan merged commit 76f74c1 into master Sep 13, 2026
2 checks passed
@Shiqan
Shiqan deleted the perf-improvements branch September 19, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant