Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Pooling: ArrayPool and friends

โญโญโญ advancedโณ 15 min read๐Ÿ“ Lesson 214 of 265

When pooling pays, how ArrayPool buckets work, and the bugs renting/returning introduce.

Pooling: ArrayPool and friends

ArrayPool<T>.Shared rents byte/T arrays from size-classed buckets (powers of two; you may get a LARGER array than requested โ€” always use your logical length, never rented.Length). Rent/Return are cheap interlocked ops; the win shows on hot paths allocating big buffers per request.

byte[] buf = ArrayPool<byte>.Shared.Rent(minLength: 4096);
try { Use(buf.AsSpan(0, logical)); }
finally { ArrayPool<byte>.Shared.Return(buf); }   // clearArray: true only for secrets

The four bugs pooling introduces (why it is opt-in, not default):

  1. Forgetting Return โ€” the buffer leaks from the pool (not the GC); the pool grows, memory balloons.
  2. Use-after-return โ€” a Span held past Return reads another renter's data. Correctness bug, possibly a security bug (cross-request data bleed).
  3. Wrong length assumptions โ€” rented.Length โ‰ฅ your need; slicing is mandatory.
  4. Returning dirty buffers with secrets โ€” clearArray: true when the content was sensitive.

When pooling PAYS: large (โ‰ฅKBs) buffers on request-rate hot paths. When it does not: small short-lived allocations (Gen0 is cheaper than the API ceremony), anything rent/return dominated by work smaller than the allocation itself. Measure with GC.GetAllocatedBytesForCurrentThread before and after โ€” the checkpoint's second test enforces this.

ObjectPool<T> (Microsoft.Extensions.ObjectPool) does the same for polymorphic objects โ€” same rules apply: rent, use, return, no leaks past return.