# Why AcBinary?

Architectural framing: where AcBinary fits in the serializer landscape, what value it adds over wire-only serializers, and how to read its benchmark numbers in that context.

> Companion docs: features detail in [`BINARY_FEATURES.md`](BINARY_FEATURES.md) · wire format in [`BINARY_FORMAT.md`](BINARY_FORMAT.md) · options/presets in [`BINARY_OPTIONS.md`](BINARY_OPTIONS.md) · streaming I/O in [`BINARY_ASYNCPIPE_ISSUES.md`](BINARY_ASYNCPIPE_ISSUES.md) + [`BINARY_ASYNCPIPE_TODO.md`](BINARY_ASYNCPIPE_TODO.md).

## Category

AcBinary serves a **different category** than wire-only serializers (Protobuf, MessagePack, MemoryPack). It is a **graph-aware** serializer with referential integrity preserved on the wire via IdTracking + StringInterning, and on the receive side via a populate-merge deserialization path that reuses existing reference identity. The primary value is **correctness and developer-ergonomics in stateful live-data scenarios** (Blazor / MAUI / WPF binding, SignalR streaming, server-push reconciliation) — **not** raw single-shot throughput on flat DTO RPC.

## Three-pillar value proposition

### 1. Graph integrity → populate-merge correctness in bound UIs

On a live-data UI, an incoming server update must merge into the in-memory graph **without** breaking binding references, child-list identity, or duplicating shared sub-objects (`Product`, `Customer`, `Partner`, etc. — entities referenced from many parent rows). Wire-only serializers always reconstruct a fresh tree per call → the consumer hand-codes the merge / rebinding logic, which is fragile and verbose. AcBinary's `IId`-keyed reference tracking preserves identity end-to-end: shared sub-objects deduplicate on the wire AND on the client, existing references survive the round-trip, bindings stay valid, change-tracking continues uninterrupted. This is the **central value** for Blazor components, `IList<T>`-bound MAUI grids, and SignalR datasource subscribers.

### 2. Bandwidth → reference + string deduplication on the wire

On real production graphs (many `Order → OrderItem → Product → Customer → Partner → GenericAttribute` references sharing the same back-end entities), IdTracking and StringInterning emit each unique object/string **once** as the full body, then 1-2 bytes per subsequent reference. Wire-only serializers re-emit the full object body every time. Bench numbers (at **20% `IId`-ref-rate** in the test fixture):

- **Latin1Long charset**: -6.9% arith / -7.4% geo wire size vs MemoryPack
- **Latin1FixAscii charset**: -18.2% arith / -21.3% geo wire size vs MemoryPack

Production graphs with higher ref-rate (the typical case — many rows pointing at the same `Product`/`Customer`) see significantly larger savings; see [How to read AcBinary benchmarks](#how-to-read-acbinary-benchmarks) below.

### 3. Streaming → memory pressure mitigation on memory-constrained hosts

`AsyncPipeReaderInput` + `AsyncPipeWriterOutput` deliver chunked I/O with **peak memory ≈ chunk-size**, not full payload size. On WASM (Mono / NativeAOT-LLVM), a 10 MB monolithic `byte[]` allocation triggers GC pressure or OOM under concurrent task load; chunked delivery keeps the working set bounded. Combined with the wire-size win, decode CPU drops proportionally — **fewer bytes on the wire → fewer varint + UTF-8 decode operations** during deserialization. SignalR's `AcBinaryHubProtocol` consumes via `AsyncPipeReaderInput` directly, no monolithic buffer materialization.

## Real-world reference: WASM SignalR receive

Measured production payload: ~10 MB / ~7900 orders (full graph `Order → OrderItem → Product → Customer → Partner → GenericAttribute`), 4 SignalR messages, Blazor WASM client.

- **Before AcBinary (MemoryPack baseline)**: ~8 seconds Deser for ~4K records / ~20 MB payload — the MemoryPack output was nearly 2× larger because the same `Product`/`Customer` entities re-serialized per Order. JSON baseline was ~30 seconds.
- **After AcBinary**: ~470 ms Deser for ~8K records / ~10 MB payload — wire compaction from graph deduplication + chunked-pipe streaming + decode-CPU reduction stack multiplicatively.

The speed-up is **not** primarily "AcBinary is faster than MemoryPack on the same bytes." It is **"AcBinary emits ~50% fewer bytes for the same graph, then decodes them with bounded memory pressure"**. The feature stack is the win — single-cell bench Ser/Deser ratios alone do not capture it.

## When AcBinary fits

- Live-data UIs requiring graph-merge into a bound client-side model (Blazor / MAUI / WPF / WinForms with two-way binding, SignalR datasource subscribers)
- SignalR / WebSocket / IPC transports with repeated entity references across messages
- Server-push reconciliation flows where ID-keyed identity must survive the wire round-trip
- Memory-bounded clients (WASM, mobile, embedded) receiving large object graphs
- Workloads where **wire-size + decode-CPU + memory-peak** matter together, not just single-message raw Ser throughput

## When AcBinary is NOT the right fit

- Single-shot DTO RPC with no entity reuse (e.g., `(int, int) → int` calculator endpoints) — wire-only serializers have less per-message overhead on flat payloads
- Schema-evolving public contracts where wire-format stability across language ecosystems is the primary requirement → Protobuf is the established choice
- Cross-platform clients on big-endian hosts → currently unsupported on the wire (see [`BINARY_ISSUES.md#accore-bin-i-e4n9`](BINARY_ISSUES.md#accore-bin-i-e4n9-wire-format-is-host-native-endian-not-canonical-little-endian))
- Append-only log formats where each record is independent — IdTracking's scan-phase overhead is wasted when there are no shared references to deduplicate

## How to read AcBinary benchmarks

`Console.FullBenchmark` test cells use **20% `IId`-ref-rate** test fixtures — flat enough to compare against wire-only serializers like MemoryPack on the same data shape. The cells **deliberately undermeasure** the feature wins:

- Production graphs typically have 50-90% `IId`-ref-rate (many rows → same `Product` / `Customer`); IdTracking's wire-size win on those graphs is **multiples larger** than the bench numbers show.
- `Repeated Strings` is the only bench cell stress-testing StringInterning. Other cells have low string-repetition, so the interning win there is bounded by the fixture.
- The bench does NOT cover the `AsyncPipe` path (only `Byte[]` and `BufferWriter` I/O modes). The streaming-memory advantage doesn't surface in the bench numbers.

Bench wins on the current fixture (-6 to -10% RT, -7 to -18% size) should be read as a **lower bound** for AcBinary's production advantage on graph-shaped, ref-heavy workloads. The matching position against MemoryPack on flat-payload Ser/Deser is **intentional** — AcBinary should not pay a tax for its features on workloads that don't benefit from them. The cases where the features pay back are workloads the bench does not measure directly; the WASM reference numbers above are the production-scale signal.
