Skip to content

perf(helpers): fast-path type assertion in AsOops before errors.As#134

Merged
samber merged 1 commit into
mainfrom
perf/asoops-fast-path
Jul 5, 2026
Merged

perf(helpers): fast-path type assertion in AsOops before errors.As#134
samber merged 1 commit into
mainfrom
perf/asoops-fast-path

Conversation

@samber

@samber samber commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • errors.As is reflection-based and AsOops is called once per layer by every chain-traversal helper (recursive, getDeepestErrorAttribute, collectMaps). When the wrapped error is directly an OopsError - the common case in oops chains - a plain type assertion answers in ~1ns.
  • errors.As remains as a fallback for chains interleaved with non-oops wrappers (fmt.Errorf %w, custom Unwrap types).

Benchmarks

benchstat vs main (darwin/arm64, Apple M3, -count=6 -cpu=1):

                │ before.bench.txt │          after.bench.txt           │
                │      sec/op      │   sec/op     vs base               │
ChainTraversal        1611.5n ± 1%   870.1n ± 0%  -46.01% (p=0.002 n=6)
ErrorDeepChain         46.47n ± 5%   46.62n ± 6%        ~ (p=0.699 n=6)
ReflectionPaths        514.3n ± 2%   533.9n ± 1%   +3.81% (p=0.002 n=6)
geomean                337.7n        278.7n       -17.46%

                │ before.bench.txt │            after.bench.txt            │
                │       B/op       │     B/op      vs base                 │
ChainTraversal      3.922Ki ± 0%     1.422Ki ± 0%  -63.75% (p=0.002 n=6)

                │ before.bench.txt │           after.bench.txt           │
                │    allocs/op     │ allocs/op   vs base                 │
ChainTraversal       14.000 ± 0%     6.000 ± 0%  -57.14% (p=0.002 n=6)

Note: BenchmarkReflectionPaths shows a stable ~4% time delta with unchanged allocations even though it does not execute AsOops in its timed loop - consistent with a binary layout artifact rather than a logical regression.

Test plan

Verified with go build ./..., go test ./..., and go test -bench=BenchmarkChainTraversal -bench=BenchmarkErrorDeepChain -bench=BenchmarkReflectionPaths -benchmem -count=6 -cpu=1 ./benchmarks run before/after the change and compared with benchstat.

errors.As is reflection-based and AsOops is called once per layer by
every chain-traversal helper (recursive, getDeepestErrorAttribute,
collectMaps). When the wrapped error is directly an OopsError - the
common case in oops chains - a plain type assertion answers in ~1ns.
errors.As remains as fallback for chains interleaved with non-oops
wrappers (fmt.Errorf %w, custom Unwrap types).

benchstat vs main (darwin/arm64, Apple M3, -count=6 -cpu=1):

                │ before.bench.txt │          after.bench.txt           │
                │      sec/op      │   sec/op     vs base               │
ChainTraversal        1611.5n ± 1%   870.1n ± 0%  -46.01% (p=0.002 n=6)
ErrorDeepChain         46.47n ± 5%   46.62n ± 6%        ~ (p=0.699 n=6)
ReflectionPaths        514.3n ± 2%   533.9n ± 1%   +3.81% (p=0.002 n=6)
geomean                337.7n        278.7n       -17.46%

                │ before.bench.txt │            after.bench.txt            │
                │       B/op       │     B/op      vs base                 │
ChainTraversal      3.922Ki ± 0%     1.422Ki ± 0%  -63.75% (p=0.002 n=6)

                │ before.bench.txt │           after.bench.txt           │
                │    allocs/op     │ allocs/op   vs base                 │
ChainTraversal       14.000 ± 0%     6.000 ± 0%  -57.14% (p=0.002 n=6)

Note: BenchmarkReflectionPaths shows a stable ~4% time delta with
unchanged allocations even though it does not execute AsOops in its
timed loop - consistent with a binary layout artifact rather than a
logical regression.
@codecov

codecov Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.18%. Comparing base (470a3a2) to head (474bac4).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #134      +/-   ##
==========================================
+ Coverage   90.17%   90.18%   +0.01%     
==========================================
  Files          15       15              
  Lines        1231     1233       +2     
==========================================
+ Hits         1110     1112       +2     
  Misses         96       96              
  Partials       25       25              
Flag Coverage Δ
unittests 90.18% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@samber samber marked this pull request as ready for review July 5, 2026 14:05
Copilot AI review requested due to automatic review settings July 5, 2026 14:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes AsOops by adding a fast-path type assertion before falling back to errors.As, reducing chain-traversal overhead when the error is directly an OopsError (the common case for oops-only wrapping chains).

Changes:

  • Add a direct err.(OopsError) type assertion fast path in AsOops.
  • Retain errors.As as a fallback to preserve behavior when chains include non-oops wrappers.
  • Document the performance rationale inline to clarify why the extra branch exists.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@samber samber merged commit ddcaa21 into main Jul 5, 2026
11 checks passed
@samber samber deleted the perf/asoops-fast-path branch July 5, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants