Prowl.Echo is built with performance as a first-class concern. Out of the box it is already faster than System.Text.Json and Newtonsoft.Json. With a handful of opt-in techniques you can push throughput higher still — outpacing even MessagePack on deserialization — while simultaneously shrinking output size.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ProwlEngine/Prowl.Echo/llms.txt
Use this file to discover all available pages before exploring further.
Benchmark Results
These numbers are from Echo’s own benchmark suite, run with[GenerateSerializer] and [FixedEchoStructure] enabled on a complex object graph: 20 nested objects with 100-element arrays, 50-entry dictionaries, and collections.
Techniques for Maximum Speed
1. Use [GenerateSerializer]
The single most impactful change you can make. Adding [GenerateSerializer] to a partial class or struct triggers Echo’s source generator, which emits a fully type-specialized ISerializable implementation at compile time.
AnyObjectFormat’s reflection loop entirely, and avoids all dictionary lookups and attribute resolution at runtime. Zero reflection overhead on the hot path.
The class or struct must be declared
partial. The generator respects all serialization attributes ([SerializeField], [SerializeIgnore], [FormerlySerializedAs], etc.) and applies them at code-generation time rather than at runtime.2. Use [FixedEchoStructure] on Small, Stable Types
For types whose field order will never change — math primitives, protocol messages, network packets — mark them with [FixedEchoStructure]. Echo serializes these positionally rather than by name, skipping the cost of writing and reading field name strings.
[GenerateSerializer] with [FixedEchoStructure] yields the maximum possible speed: generated code with positional layout, no reflection, and no field-name overhead.
3. Use Binary Format for I/O
Text formats carry significant overhead from UTF-8 encoding, string escaping, and parsing. For any workload where I/O speed matters, useEchoBinaryFormat:
Techniques for Minimum Serialized Size
1. [FixedEchoStructure] Skips Field Names
Because positional serialization omits field name strings from the output, [FixedEchoStructure] reduces size as well as improving speed. For types with many short-lived fields — such as game component state or network messages — the savings compound quickly.
2. BinaryEncodingMode.Size with LEB128
EchoBinaryFormat supports two encoding modes. Set EncodingMode = BinaryEncodingMode.Size to switch from fixed-width integers to LEB128 variable-length encoding and LZW string compression:
BinaryEncodingMode.Performance
Fixed-width integers. Fastest read/write speed. Larger output. Default mode.
BinaryEncodingMode.Size
LEB128 integers, LZW-compressed strings. Smallest possible output. Slightly slower I/O. Best for network transmission or storage-constrained environments.
The Primitive Fast Path
Primitives and strings bypass the full formatter pipeline entirely. When Echo encounters a primitive value type orstring as the declared field type, it short-circuits directly to a switch on TypeCode and returns a pre-allocated EchoObject with no format lookup, no cache access, and no attribute evaluation.
Types that take this fast path:
int, float, double, bool, string, long, byte, char, uint, short, ulong, ushort, sbyte, decimal, byte[]
This means that objects composed primarily of primitive fields — which covers the majority of game data — are serialized and deserialized with minimal overhead even without source generation.
TypeMode.None — Skip Type Embedding
By default (TypeMode.Auto), Echo embeds type information in the output when the runtime type differs from the declared field type. This supports polymorphism and round-trips where the caller doesn’t know the type ahead of time.
If you always know the exact type at the call site, use TypeMode.None to skip embedding type metadata entirely. Output is smaller and the serialize/deserialize path is shorter:
Cache Management
Reuse SerializationContext Across Calls
SerializationContext carries the reference-tracking dictionaries (objectToId / idToObject) used to detect and resolve circular references. Allocating a new context per call is fine for isolated objects, but if you are serializing a set of objects that share references — for example, a scene graph or an asset bundle — reuse the same context across all calls to avoid re-allocating those dictionaries and to preserve cross-object reference identity:
Serializer.ClearCache()
Echo caches the format resolution result for every type it has seen, the serializable field list for every type (with pre-computed attribute data), and the type name registry. These caches are permanent for the lifetime of the process under normal conditions.
Call Serializer.ClearCache() after hot-reloading assemblies, when a new version of a type has been loaded into the process and you need Echo to re-inspect it:
Summary
Add [GenerateSerializer] to your types
Mark hot types as
partial and add [GenerateSerializer]. This is the largest single improvement available and eliminates reflection entirely for those types.Add [FixedEchoStructure] to stable small types
Apply to math structs, protocol messages, and any type whose field order is frozen. Reduces both size and serialization time.
Switch to EchoBinaryFormat for file/network I/O
Replace text formats with
EchoBinaryFormat. Use BinaryEncodingMode.Size when output size is a constraint.Use TypeMode.None when the type is always known
Eliminates type envelope overhead when both the serializer and the deserializer know the concrete type at compile time.