Add _lite variants that skip backtrace capture#454
Closed
FuuuOverclocking wants to merge 2 commits into
Closed
Conversation
Provide zero-backtrace-cost alternatives for performance-critical paths: - Error::new_lite() / Error::msg_lite() - .context_lite() / .with_context_lite() - anyhow_lite!() macro - Error::backtraced() to retroactively capture at a cold boundary These pass None as the backtrace parameter unconditionally, avoiding the ~4us fixed overhead of Backtrace::capture() while retaining the same error-chaining semantics. The backtraced() method allows "upgrading" a lite error with a backtrace at the point where it leaves the hot path, so the trace starts from the cold boundary rather than being lost entirely. Intended usage: keep the lightweight error type inside hot loops and convert to full anyhow::Error (with backtrace) at cold API boundaries. Signed-off-by: Fuu <[email protected]>
c70d0f6 to
0c35299
Compare
Introduce benches/error_cost.rs measuring the cost tiers: thiserror construct: ~0 ns (optimized away entirely) anyhow_lite!/context_lite: ~15 ns (heap alloc, no backtrace) anyhow!/context (BT=1): ~4.2 us (heap alloc + backtrace capture) Also includes stack-depth scaling tests (8/32/128 frames) and a run_bench.sh script that drives three rounds: BT=0, BT=1 (DWARF CFI), BT=1 + frame-pointers. Key findings on Intel Xeon 6982P-C: - Backtrace capture is a ~4.2us fixed cost, independent of stack depth and unwind method (DWARF vs frame-pointer differ by only 1-3%) - The _lite variants remain constant at ~15 ns regardless of RUST_LIB_BACKTRACE, confirming a ~260x reduction Signed-off-by: Fuu <[email protected]>
0c35299 to
286d03a
Compare
dtolnay
reviewed
Jul 4, 2026
dtolnay
left a comment
Owner
There was a problem hiding this comment.
Thanks for the PR. I would prefer not to support this in this crate and instead encourage anything that would consider using these APIs to switch to a different error type.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Backtrace::capture()incurs a fixed ~4 us overhead peranyhow::Errorconstruction whenRUST_LIB_BACKTRACE=1. For most applications this is acceptable, but on hot paths where errors are constructed at high frequency, it becomes a bottleneck. Today the only escape hatch is globally disabling backtraces via environment variable, which sacrifices debuggability everywhere.Solution
Introduce
_litevariants that unconditionally skip backtrace capture:Error::new(e)Error::new_lite(e)Error::msg(m)Error::msg_lite(m)anyhow!(...)anyhow_lite!(...).context(c).context_lite(c).with_context(f).with_context_lite(f)Plus
Error::backtraced()to retroactively capture a backtrace at a cold boundary, upgrading a lite error without losing the trace entirely.All variants retain identical error-chaining and downcast semantics.
Benchmark Results (Intel Xeon 6982P-C, pinned core)
thiserrorconstructanyhow_lite!()anyhow!().context_lite().context()Stack depth scaling (BT=1 DWARF):
anyhow!()anyhow_lite!()The lite variants are ~260x faster, constant at ~15 ns regardless of backtrace settings or stack depth.
Usage
FAQ: Why not use
thiserroron the hot path from the start?anyhowminimizes boilerplate during early development; optimization should be driven by profiling, not premature decisions.anyhow::Resultto a custom enum touches every signature and?site. The_litesuffix is a one-token change at the call site with the same performance benefit.Design Notes
_litemethods passNonefor the backtrace parameter to existing internal constructors — no new unsafe code, no architectural changes.backtraced()is idempotent: if a backtrace already exists, it's a no-op.#[cfg(feature = "std")]gates all backtrace-related additions; no-std builds are unaffected.