# Scratch Buffer Strategy

> Choose how a hot path gets a short-lived scratch buffer - zeroed `stackalloc`, `[SkipLocalsInit]` + `stackalloc`, `BufferScope` of T (stack with pool fallback), or a rental from the shared `ArrayPool` of T - and apply the net481/net10 size crossovers. Use when designing or reviewing a performance-sensitive path that needs a temporary buffer, when deciding "should I rent or stackalloc?", when weig…

- **Type:** Skill
- **Install:** `agentstack add skill-jeremykuhne-agent-skills-scratch-buffer-strategy`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [JeremyKuhne](https://agentstack.voostack.com/s/jeremykuhne)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [JeremyKuhne](https://github.com/JeremyKuhne)
- **Source:** https://github.com/JeremyKuhne/agent-skills/tree/main/skills/scratch-buffer-strategy

## Install

```sh
agentstack add skill-jeremykuhne-agent-skills-scratch-buffer-strategy
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Scratch buffer strategy (net481 + net10)

If `overlay.md` exists beside this file, read it before acting; it contains
repository-specific bindings. This core remains usable without it.

Pick the cheapest correct way to get a short-lived scratch buffer on a hot path.
This skill is the compact decision aid; the measured numbers, the per-call
ArrayPool autopsy, the inlining traps, and the references live in the bundled
[references/arraypool-performance.md](references/arraypool-performance.md). Read
that doc before quoting a number or making a non-obvious call.

A multi-targeted library runs on **net481** (older RyuJIT: unvectorized `memset`,
no tiered JIT/PGO, weaker inliner) and **net10**. Buffer costs differ enough
between them that every decision must hold on both. Name the JIT in any writeup
(the JIT-naming rule).

## Four options, ranked for the common case

1. **`[SkipLocalsInit]` + `stackalloc`** - fastest (~1-2 ns, both TFMs).
   Requires: fixed/bounded size, stack-safe, and **every read slot written
   first**. Put the attribute on the method that physically contains the
   `stackalloc`.
2. **Zeroed `stackalloc`** - safe default. Cost scales with byte count;
   net481 zeroing is ~3.6x net10 for a **compile-time-constant** size (for a
   runtime-variable size both TFMs zero at ~the same rate - see the doc).
3. **`BufferScope(stackalloc T[N], minimumLength)`** - variable/unbounded
   size that is usually small. Stays on the stack for the common case, rents
   from `ArrayPool` only on overflow. Wrapper overhead ~1 ns net481 / ~0.3 ns
   net10. Forwards the caller's `[SkipLocalsInit]` to the stack buffer.
   (`BufferScope` ships as `Touki.Buffers.BufferScope` in the
   [`KlutzyNinja.Touki`](https://www.nuget.org/packages/KlutzyNinja.Touki) NuGet
   package - consume that rather than rolling or supplying your own.)
4. **`ArrayPool.Shared` Rent/Return** - large, unbounded, recursive, or must
   escape the frame. Has a fixed per-call floor that warmup never removes
   (~10 ns/op net481, ~4 ns/op net10). Rent **once** and reuse across the loop.

## Decision tree

```text
Short-lived scratch buffer on a hot path?
|
+- Fixed/small (one frame, few KB), not recursive/looped?
|   +- Write every slot before reading?  --> [SkipLocalsInit] + stackalloc
|   +- Cannot guarantee that?            --> zeroed stackalloc (or zero only
|                                            the prefix you read)
|
+- Usually small but occasionally large (variable size)?
|   --> BufferScope(stackalloc T[N], minLen); put [SkipLocalsInit] on the
|       CALLING method to drop the stack-buffer zeroing
|
+- Large / unbounded / recursive / must escape frame?
    --> ArrayPool.Shared, rented once and reused
```

## Crossover thresholds (zeroed stackalloc vs a rental)

For a **runtime-variable** size, zeroing a `stackalloc` is cheaper than renting
below these sizes; above them the rental's flat floor wins (only if you can use
the memory uninitialized):

| Rental kind | net481 | net10 |
| --- | --: | --: |
| Warm TLS hit (first rental) | ~1.3 KB | ~190 B |
| Locked (second same-bucket rental) | ~3 KB | ~1.8 KB |

A **compile-time-constant** size zeroes far cheaper on net10, pushing its
crossover higher. If you would have to `Clear()` the rented array, add that cost
back to the rental side - the crossover then exceeds any sane stack size, so
just stack-allocate.

## Load-bearing facts (do not re-derive)

- **net481 honors `[SkipLocalsInit]`.** The `localsinit`-flag suppression works
  on the desktop CLR exactly as on CoreCLR; the attribute *type* is
  source-generated downlevel by PolySharp. A 4 KB clear drops from ~53 ns to
  ~1.7 ns on net481.
- **Only byte count drives zeroing cost** - struct arrays zero exactly like
  primitive arrays of the same size.
- **A warm pool is not a free pool.** Rent/Return pays bucket-index math, a
  one-deep TLS cache (two same-bucket rentals collide into the locked stack),
  and a separate pool per element type - on every call, net481 ~2.5x worse.
- **`[SkipLocalsInit]` hands you uninitialized memory.** Use it only where a
  profile shows the clear is hot, every read slot is provably written first, and
  no unwritten bytes can escape (info-disclosure risk). The JIT inliner does not
  spread the attribute; net481's weaker inliner can move where zeroing lands, so
  verify on both TFMs.
- **`Unsafe.SkipInit` does NOT suppress the zeroing.** Common mistake: assuming
  `Unsafe.SkipInit(out x)` makes a stack buffer un-zeroed. It compiles to a bare
  `ret` and only satisfies C# definite-assignment so you can read an unassigned
  local; the runtime still zeroes via the method's `.locals init` flag, which
  only `[SkipLocalsInit]` (or `[module: SkipLocalsInit]`) removes. A "fixed
  buffer left uninitialized with `Unsafe.SkipInit`" but *without*
  `[SkipLocalsInit]` pays full zeroing. See
  [references/arraypool-performance.md](references/arraypool-performance.md)
  section 8.

## Stack-safety guardrail

`stackalloc` only belongs on the stack at all if it is bounded: keep a single
frame's `stackalloc` to a few KB, and never `stackalloc` on a recursive or
unbounded-loop path (CA2014). Default Windows managed stack is 1 MB, but library
code may run on a 256 KB partial-trust thread. Past those limits, rent once and
reuse instead.

## Related

- [references/arraypool-performance.md](references/arraypool-performance.md) -
  the full measured tables, the ArrayPool per-call autopsy, the `BufferScope`
  overhead numbers, the inlining trap, and references. **This is the backing
  data; cite it.**
- The repository's performance-testing and framework-JIT-optimization skills
  (author/run the BenchmarkDotNet benchmarks that produced these numbers, and
  net481 loop tuning once a buffer choice is made) - a consuming repository
  wires the concrete cross-references in its overlay.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [JeremyKuhne](https://github.com/JeremyKuhne)
- **Source:** [JeremyKuhne/agent-skills](https://github.com/JeremyKuhne/agent-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-jeremykuhne-agent-skills-scratch-buffer-strategy
- Seller: https://agentstack.voostack.com/s/jeremykuhne
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
