# rlsp-yaml-parser Benchmark Results

Comparison of `rlsp-yaml-parser` against `libfyaml` (a high-performance C YAML library).

## Environment

| Property | Value |
|----------|-------|
| rustc | 1.94.1 (e408947bf 2026-03-25) |
| CPU | Intel(R) Core(TM) Ultra X7 358H |
| Architecture | x86_64 |
| Platform | Linux (container) |

> **Note:** These results are from a containerized environment. Container scheduling and shared CPU resources introduce noise compared to bare-metal measurements. Use these numbers for relative comparisons between APIs — not as absolute performance claims.

## Methodology

Benchmarks use [Criterion.rs](https://github.com/bheisler/criterion.rs) with 10 samples per group.

### APIs benchmarked

| API | Description |
|-----|-------------|
| `rlsp::load` | Full tree construction — parses YAML and builds an in-memory `Node` tree with spans and comments |
| `rlsp::parse_events` | Event streaming — yields `Event` values without building a tree; comparable to libfyaml's event API |
| `libfyaml::parse_events` | C event drain — libfyaml's `fy_parser_parse` loop via FFI; allocates events in C heap (not tracked by the Rust counting allocator) |

The **fair comparison** for throughput is `rlsp::parse_events` vs `libfyaml::parse_events` — both drain all events without building a persistent tree. `rlsp::load` adds tree construction overhead and is included to show the full-pipeline cost.

### Fixtures

**Synthetic fixtures** (generated by `benches/fixtures.rs`):

| Name | Size | Style |
|------|------|-------|
| tiny_100B | ~100 B | mixed |
| medium_10KB | ~10 KB | mixed |
| large_100KB | ~100 KB | mixed |
| huge_1MB | ~1 MB | mixed |
| block_heavy | ~100 KB | deeply nested block mappings |
| block_sequence | ~100 KB | block sequence of scalars |
| flow_heavy | ~100 KB | flow mapping objects |
| scalar_heavy | ~100 KB | plain, quoted, single-quoted, and literal block scalars |
| mixed | ~100 KB | interleaved block, flow, and scalar constructs |

**Real-world fixture:** A Kubernetes `Deployment` manifest (~3 KB), representative of typical LSP input.

### Memory measurement

Allocation bytes and count are measured using a `CountingAllocator` that wraps the Rust system allocator. This intercepts all Rust heap allocations during `load()` and `parse_events()`. It does **not** intercept `malloc` calls from libfyaml's C code — memory benchmarks cover rlsp only.

## Results

### Throughput by document size

Median throughput (MB/s) from Criterion. The `rlsp/load` column includes tree construction; `rlsp/events` is event-only.

| Fixture | rlsp/load | rlsp/events | libfyaml/events | rlsp/events vs libfyaml |
|---------|----------:|------------:|----------------:|------------------------:|
| tiny_100B (~100 B) | 726 KiB/s | 494 KiB/s | 33.1 MiB/s | 67× slower |
| medium_10KB (~10 KB) | 590 KiB/s | 591 KiB/s | 98.9 MiB/s | 167× slower |
| large_100KB (~100 KB) | 578 KiB/s | 583 KiB/s | 112 MiB/s | 192× slower |
| huge_1MB (~1 MB) | 306 KiB/s | 309 KiB/s | 105 MiB/s | 339× slower |

Raw timings (median):

| Fixture | rlsp/load | rlsp/events | libfyaml/events |
|---------|----------:|------------:|----------------:|
| tiny_100B | 151.95 µs | 223.22 µs | 3.26 µs |
| medium_10KB | 16.57 ms | 16.55 ms | 96.6 µs |
| large_100KB | 168.96 ms | 167.51 ms | 851.6 µs |
| huge_1MB | 3.196 s | 3.165 s | 9.07 ms |

### Throughput by YAML style (~100 KB each)

| Style | rlsp/load | rlsp/events | libfyaml/events | rlsp/events vs libfyaml |
|-------|----------:|------------:|----------------:|------------------------:|
| block_heavy | 509 KiB/s | 519 KiB/s | 95.8 MiB/s | 184× slower |
| block_sequence | 1.002 MiB/s | 1.036 MiB/s | 197 MiB/s | 190× slower |
| flow_heavy | 595 KiB/s | 591 KiB/s | 83.6 MiB/s | 141× slower |
| scalar_heavy | 1.029 MiB/s | 1.064 MiB/s | 189 MiB/s | 178× slower |
| mixed | 582 KiB/s | 585 KiB/s | 109 MiB/s | 186× slower |

### Throughput — real-world (Kubernetes Deployment, ~3 KB)

| API | Time (median) | Throughput |
|-----|-------------:|----------:|
| rlsp/load | 3.916 ms | 970 KiB/s |
| rlsp/parse_events | 3.812 ms | 996 KiB/s |
| libfyaml/parse_events | 28.67 µs | 129 MiB/s |

### Latency — time to first event

| Fixture | rlsp/first_event | libfyaml/first_event | ratio |
|---------|----------------:|---------------------:|------:|
| tiny_100B | 148.82 µs | 949 ns | 157× |
| medium_10KB | 16.02 ms | 923 ns | 17,357× |
| large_100KB | 174.84 ms | 933 ns | 187,398× |
| huge_1MB | 3.498 s | 918 ns | 3,810,241× |
| kubernetes_3KB | 3.671 ms | 947 ns | 3,879× |

> **Note:** libfyaml's near-constant ~950 ns first-event latency across all document sizes reflects lazy parsing — it starts emitting events before scanning the full input. rlsp-yaml-parser currently scans and tokenizes the entire input before yielding the first event, so first-event latency scales linearly with document size.

### Latency — full event drain

| Fixture | rlsp/parse_events | libfyaml/parse_events |
|---------|------------------:|----------------------:|
| tiny_100B | 223.14 µs | 3.159 µs |
| medium_10KB | 16.16 ms | 89.9 µs |
| large_100KB | 170.35 ms | 858.2 µs |
| kubernetes_3KB | 3.782 ms | 28.19 µs |

### Memory allocation profile (rlsp only)

Measured with `CountingAllocator`; single parse in a release build.

#### `load()` — full tree construction

| Fixture | Bytes allocated | Allocations |
|---------|---------------:|------------:|
| tiny_100B (~100 B) | 317,823 | 10,237 |
| medium_10KB (~10 KB) | 24,003,948 | 763,857 |
| large_100KB (~100 KB) | 225,991,169 | 7,216,701 |
| kubernetes_3KB (~3 KB) | 2,557,802 | 68,731 |

#### `parse_events()` — event streaming

| Fixture | Bytes allocated | Allocations |
|---------|---------------:|------------:|
| tiny_100B (~100 B) | 306,768 | 10,216 |
| medium_10KB (~10 KB) | 23,284,255 | 762,601 |
| large_100KB (~100 KB) | 219,005,984 | 7,205,058 |
| kubernetes_3KB (~3 KB) | 2,482,386 | 68,609 |

The allocation overhead per byte is approximately 2.19 KB allocated per input byte (`load`, large fixture: 225 MB / 100 KB), reflecting that rlsp preserves full span information, comments, and a complete AST node per token — significantly more metadata per node than a bare event stream.

## Analysis

### Event-level comparison: ~150–340× throughput gap

On the fair apples-to-apples comparison (`rlsp::parse_events` vs `libfyaml::parse_events`), libfyaml is 150–340× faster. The gap grows with document size, suggesting O(n²) behavior in rlsp's parser for large inputs (the huge_1MB fixture takes 3.165 s vs 9.07 ms). For documents up to 10 KB — the typical LSP file size — the 167× gap means ~16 ms vs ~97 µs per parse.

### rlsp/load vs rlsp/parse_events: tree construction is cheap

For medium and large fixtures, `load()` and `parse_events()` have nearly identical throughput (590 vs 591 KiB/s at 10 KB; 578 vs 583 KiB/s at 100 KB). The tree construction step itself is not the bottleneck — the parser and tokenizer dominate. The allocation count difference is small (~1,256 extra allocs for load vs parse_events at 10 KB).

### libfyaml first-event latency is O(1); rlsp is O(n)

libfyaml emits its first event in ~950 ns regardless of document size (constant). rlsp's first-event time tracks full-parse time, confirming it buffers the entire input before yielding. For the LSP use case — where the server wants to respond before a large document finishes loading — this is the most significant functional gap. A streaming tokenizer that yields events incrementally would bring first-event latency down to O(1).

### Style sensitivity

Flow-heavy YAML is ~2× slower than block sequences for both parsers. libfyaml handles `block_sequence` fastest (197 MiB/s), while rlsp's `scalar_heavy` and `block_sequence` benchmarks show its best throughput (1.0–1.1 MiB/s), both consistent with less nested structure being cheaper to parse.

### Allocation rate is very high

rlsp allocates ~2.2 KB of heap per byte of input for `load()`, and ~2.1 KB per byte for `parse_events()`. The counts (~72 allocations per byte of input at the Kubernetes fixture scale) indicate fine-grained allocation per token or span. Reducing allocations — via arena allocation or pooling — would be the highest-impact memory optimization.

### Trade-off: correctness and span fidelity vs raw speed

libfyaml is a production C library optimized for speed. rlsp-yaml-parser is a spec-faithful Rust implementation that preserves lossless byte-range spans and comments — information that libfyaml discards. The throughput gap is the cost of that fidelity. For an LSP server that needs to report exact diagnostic positions and preserve comments for formatting, the span data is necessary; raw throughput is not the primary design goal.
