From 474bac4735501df9217a4cc622427dd22f24fb98 Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Sun, 5 Jul 2026 16:00:20 +0200 Subject: [PATCH] perf(helpers): fast-path type assertion in AsOops before errors.As MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- helpers.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/helpers.go b/helpers.go index 1b4d12d..5e91b2b 100644 --- a/helpers.go +++ b/helpers.go @@ -32,6 +32,14 @@ import "errors" // retryOperation() // } func AsOops(err error) (OopsError, bool) { + // Fast path: a direct type assertion avoids the reflection cost of + // errors.As. Chain traversal helpers (recursive, getDeepestErrorAttribute, + // collectMaps) call AsOops once per layer, so this dominates the cost of + // every accessor on deeply wrapped errors. + if e, ok := err.(OopsError); ok { + return e, true + } + var e OopsError ok := errors.As(err, &e) return e, ok