.agents/skills/performance-fixer/references/decision-framework.md
Use this to decide whether a candidate optimization is worth applying before you spend effort proving it. The goal is not maximum micro-optimization everywhere — it is to spend complexity budget where it buys the most, and leave simple code simple where perf does not matter. In SkiaSharp specifically, the wins worth chasing are the managed layer's own overhead between the caller and Skia, not Skia's C++ rasterizer (that is upstream).
Score every candidate on two axes:
Optimize by impact ÷ complexity (return on investment). The 80/20 rule holds: a few low-complexity patterns deliver most of the available wins.
Complexity is how much the change adds to the cost of reading, reasoning about, and safely maintaining the code — independent of how advanced the API looks. A sophisticated API used in a localized, self-evidently-correct way is low; a simple-looking change that plants a fragile non-local invariant is not. Score by the dominant of:
Levels:
Try* overload,
pooling via Utils.RentArray, sizing a collection, sealed, [MethodImpl(AggressiveInlining)]
on a trivial wrapper, a stackalloc with a self-evident write-before-read, a blittable
MemoryMarshal.Cast<SKColor,uint>.hot-paths/handles-and-collections.md),
an in/ref readonly on a large struct, a reinterpret whose correctness depends on layout.hot-paths/geometry-math.md), manual
SIMD, unsafe/raw-pointer buffer juggling, any change to the HandleDictionary locking.Report high-complexity options, don't hide them. When the fastest approach is medium/high and you recommend a simpler one, still report the faster option and its tradeoff so the maintainer can choose.
The same change is mandatory or pointless depending on where it runs. Ask: how many times does this execute under load, and does it sit between the caller and a result they are waiting on?
SKCanvas, SKSurface),
per-point/per-vector geometry (SKMatrix map/invert/concat), per-glyph text/shaping
(SKFont, SkiaSharp.HarfBuzz), per-pixel/scanline buffer copies (SKBitmap, SKPixmap),
startup color/theme hydration (SKColor.Parse), and the HandleDictionary (touched on every
native object creation).SKFontManager/typeface enumeration done once,
configuration, error paths. Prefer the simplest code; allocations there are fine.Optimize the hot list aggressively; leave the cold list simple. A wasteful method on a cold path is not a finding.
This is where SkiaSharp differs from a general perf pass. A candidate only becomes a fix when both hold (details in measuring.md):
New vs Old comparison shows a meaningful, repeatable
speedup outside the Mean/Error/StdDev bands, stable across ≥2 runs, with no allocation
regression, on a realistically shaped workload.noop)SkiaSharp is mature; much of the obvious overhead is already optimized. Standing down on a hardened surface is the expected outcome, not a failure. It is not a finding when:
Vector256 is emulated and ran
5.7–6.5× slower on ARM64 NEON in #4241. A win on x64 that regresses ARM64 is not shippable
without correct per-runtime gating.Never manufacture a 2% micro-win on a synthetic loop no real caller hits.