This project ships a ready-to-run flamegraph setup for finding CPU hot spots in the scanner, parser, and serde data-format paths. A flamegraph is a sampled view of where wall-clock time goes: box width is the share of samples a function (plus its callees) was on the stack, and the boxes stacked above a function are the things it called.
Install the cargo flamegraph subcommand once:
cargo install flamegraphPlatform profiler (used under the hood):
- macOS —
dtrace, bundled with the OS. Requiressudo, which the aliases handle via--root(you will be prompted for your password). With System Integrity Protection enabled, dtrace works on binaries you build yourself (our benches) but not on system binaries — no extra setup needed here. - Linux —
perf(e.g.apt install linux-tools-$(uname -r)). If you hitperf_event_paranoiderrors, either keep the--rootthe aliases pass, or relax it:sudo sysctl kernel.perf_event_paranoid=1.
Three aliases are defined in .cargo/config.toml:
cargo flame-serde-load # from_str::<T> hot path (serde feature)
cargo flame-serde-dump # to_string hot path (serde feature)
cargo flame-parsing # core scanner/parser/composer (no serde)Each produces flamegraph.svg in the repo root. Open it in a browser — it
is interactive: click any box to zoom into that subtree, and use the search box
to highlight a function across the graph. The artifact is git-ignored.
Taking flame-serde-load as the example:
cargo flamegraph --root --profile profiling --features serde \
--bench serde_load -- --profile-time 12 rust_yaml/large
| Flag | Why |
|---|---|
--root |
dtrace/perf needs elevated privileges (sudo prompt on macOS). |
--profile profiling |
Builds optimized code with debug symbols (see [profile.profiling] in Cargo.toml), so stacks are both realistic and readable. |
--features serde |
The serde benches are gated behind required-features = ["serde"]. |
--bench serde_load |
Target the Criterion bench, not a unit test. |
-- --profile-time 12 |
Puts Criterion in profiler mode: run the benchmark for ~12s with no statistical analysis or plotting — exactly what an external sampler wants. |
rust_yaml/large |
Regex filter so we profile our code path on the large fixture, not the serde_yaml comparison case. |
To profile a different case, run the underlying command directly and change the
filter, e.g. rust_yaml/small or drop the filter to capture everything:
cargo flamegraph --root --profile profiling --features serde \
--bench serde_load -- --profile-time 12 rust_yaml/medium- Width = cost. A wide box was on the stack for a large share of samples. Width is cumulative (function + everything it called), so a wide box with a single wide child is just a pass-through — look at the flat top edges, which is where CPU time is actually spent.
- The serde
from_str1.5–1.8x gap reported in PR #67 should show up as time split acrossScanner/tokenizing, scalar resolution (resolve_plain_scalarand friends), and the serdeVisitorbridge fromValueinto the derived struct. Use the graph to confirm which dominates before optimizing.
-
Truncated /
[unknown]stacks through inlined code. LTO inlines aggressively. Opt in to frame pointers for one run (not committed, to keep CI/release builds untouched):RUSTFLAGS="-C force-frame-pointers=yes" cargo flame-serde-load -
dtrace: failed to initialize/ permission denied (macOS). Make sure the command kept--root; rerun and enter your password at the prompt. -
Empty or near-empty graph. The benchmark ran too briefly to collect samples — increase
--profile-time(e.g.24).