Skip to content

High-performance inline data structures for .NET with zero/low allocations, designed for hot paths and systems-level workloads.

Notifications You must be signed in to change notification settings

aw0lid/InlineCollections

Repository files navigation

NuGet Version .NET 8.0+ License: MIT Allocations: Zero Performance: Ultra--Low--Latency

โšก InlineCollections

InlineCollections provides high-performance, zero-allocation collection primitives for .NET with a fixed capacity of 32 elements. The collections are implemented as ref struct types optimized for ultra-low latency scenarios where heap allocations must be eliminated.

๐Ÿš€ Overview

InlineList32<T>, InlineStack32<T>, and InlineQueue32<T> provide stack-allocated storage via the InlineArray language feature (C# 12+), enabling:

  • โœจ Zero heap allocations for the fast path
  • ๐ŸŽ๏ธ Minimal GC pressure
  • ๐ŸŽฏ Predictable memory layout for cache optimization
  • ๐Ÿ›ก๏ธ High-throughput, low-latency execution

Important

Positioning Statement: This library is not a general-purpose replacement for the standard BCL collections types. Standard collections are designed for flexibility and large datasets. InlineCollections are "surgical tools" designed for High-Performance hot-paths where the developer has a guaranteed bound on the number of elements (โ‰ค 32) and must eliminate heap allocations to reduce GC pressure and latency.


๐Ÿ› ๏ธ Getting Started

๐Ÿ“ฆ Installation

Add the package to your project via .NET CLI:

dotnet add package InlineCollections --version 0.1.0

๐Ÿš€ Quick Start & Usage

InlineCollections are ref struct types designed for stack allocation. They ensure Zero Heap Allocation for up to 32 elements.

using InlineCollections;

// Initialize on stack (Zero Allocation)
var list = new InlineList32<int>();

list.Add(10);
list.Add(20);

// High-performance iteration (Modify in-place via Span)
foreach (ref int item in list.AsSpan())
{
    item += 1; 
}

๐Ÿ’ก Why this library exists

Standard .NET collections (List<T>, Stack<T>, Queue<T>) allocate on the heap, requiring GC overhead and cache misses for small working sets. In high-performance scenarios (real-time systems, game engines, network processors, serialization hotspots), this overhead is unacceptable. InlineCollections eliminates allocations by storing 32 elements inline within the struct itself.

Performance highlights

  • โšก Zero Allocations โ€” for up to 32 elements these collections avoid heap allocations.
  • ๐Ÿ—‘๏ธ Reduced GC Pressure โ€” fewer short-lived allocations means fewer GC cycles and pauses.
  • โš–๏ธ Predictable Latency โ€” bounded-capacity operations reduce variance in execution time.

โœ… When to use

  • โšก Hot-path code that creates many short-lived small collections
  • โฑ๏ธ Real-time systems requiring predictable latency
  • ๐ŸŽฎ Game engine frame-local processing (per-frame temporary buffers)
  • โšก Network packet processing and protocol parsing
  • ๐Ÿ” Serialization/deserialization buffers where allocations matter
  • ๐Ÿง  Stack-like or frame-local data with bounded depth

When NOT to use

  • Collections that routinely exceed 32 elements
  • Scenarios requiring thread-safety or concurrent access
  • Reference-type or nullable element types
  • When API compatibility with List<T> is required
  • Managed heap scenarios where GC pressure is not a primary concern

How it works

Memory model

Each collection type uses the InlineArray32<T> struct, which leverages the [InlineArray(32)] attribute to embed 32 elements directly inside the struct. This is a value-type collection stored on the stack (when not captured in a reference type).

  • Inline storage: 32 elements stored as struct fields
  • No heap allocation
  • ref struct semantics (no boxing, no reference storage)

Constraints

  • Unmanaged element types only (constraint: T : unmanaged, IEquatable<T>)
  • Fixed capacity of exactly 32 elements
  • Throws InvalidOperationException when capacity is exceeded
  • Value-type semantics: copies on assignment/parameter passing

Collections provided

InlineList32

A list with a maximum capacity of 32 unmanaged elements.

Key methods:

  • Add(T item) โ€” add to end; throws if full
  • TryAdd(T item) โ€” add to end; returns false if full
  • AddRange(ReadOnlySpan<T> items) โ€” bulk add
  • Insert(int index, T item) โ€” insert at index
  • Remove(T item) โ€” remove first occurrence
  • RemoveAt(int index) โ€” remove by index
  • T this[int index] โ€” indexer with ref return for in-place modification
  • Span<T> AsSpan() โ€” get current elements as a span
  • Contains(T item) โ€” linear search
  • Clear() โ€” empty the list

Example:

var list = new InlineList32<int>();
list.Add(1);
list.Add(2);
list.Add(3);
int value = list[0];  // 1

var span = list.AsSpan();
foreach (var item in span) {
    Console.WriteLine(item);
}

foreach (var item in list) {
    Console.WriteLine(item);
}

InlineStack32

LIFO (Last-In-First-Out) collection with maximum capacity of 32 elements.

Key methods:

  • Push(T item) โ€” push to stack; throws if full
  • TryPush(T item) โ€” push; returns false if full
  • T Pop() โ€” pop and return; throws if empty
  • bool TryPop(out T result) โ€” pop safely
  • ref T Peek() โ€” return ref to top without removing; throws if empty
  • Span<T> AsSpan() โ€” get all elements (in insertion order)
  • Clear() โ€” empty the stack

Example:

var stack = new InlineStack32<int>();
stack.Push(10);
stack.Push(20);
stack.Push(30);

int top = stack.Pop();  // 30
int next = stack.Pop(); // 20

ref int peek = ref stack.Peek();
peek = 100;  // modify in-place

foreach (var item in stack) {
    Console.WriteLine(item);  // Iterates in reverse order (LIFO)
}

InlineQueue32

FIFO (First-In-First-Out) collection with maximum capacity of 32 elements. Internally uses a circular buffer for O(1) enqueue/dequeue.

Key methods:

  • Enqueue(T item) โ€” add to back; throws if full
  • TryEnqueue(T item) โ€” add safely; returns false if full
  • T Dequeue() โ€” remove and return from front; throws if empty
  • bool TryDequeue(out T result) โ€” dequeue safely
  • ref T Peek() โ€” return ref to front without removing; throws if empty
  • Clear() โ€” empty the queue

Example:

var queue = new InlineQueue32<int>();
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);

int first = queue.Dequeue();  // 1
int second = queue.Dequeue(); // 2

ref int front = ref queue.Peek();
front = 999;  // modify in-place

foreach (var item in queue) {
    Console.WriteLine(item);  // Iterates in FIFO order
}

Limitations and exceptions

  • Fixed capacity: Exactly 32 elements; exceeding capacity throws InvalidOperationException.
  • Unmanaged types only: T must satisfy T : unmanaged, IEquatable<T>.
  • Value semantics: Assignment and parameter passing copy the entire struct.
  • Struct size: Each collection is 32 * sizeof(T) bytes plus overhead. Large T types increase stack usage.
  • ref struct: Cannot be stored in fields of reference types or classes; cannot be boxed.
  • No null elements: Elements must be valid unmanaged values.
  • Exceptions:
    • InvalidOperationException โ€” capacity exceeded or collection is empty (on Pop/Peek/Dequeue without Try- variant)
    • ArgumentOutOfRangeException โ€” invalid index (RemoveAt)

Performance characteristics

All operations are O(1) constant time except:

  • Remove(T item) โ€” O(n) linear search and shift
  • RemoveAt(int index) โ€” O(n) shifts remaining elements
  • Insert(int index, T item) โ€” O(n) shifts elements to the right

Indexer access (this[int index]) and Peek/Pop operations have zero bounds checking and are aggressively inlined.

For detailed benchmarks and comparisons with List<T>, Stack<T>, and Queue<T>, see docs/benchmarks.md.

Documentation

Benchmarks

BenchmarkDotNet results comparing InlineCollections with standard BCL collections are available in the benchmarks/ directory. Run:

dotnet run --project benchmarks/InlineCollections.Benchmarks -c Release

Key findings:

  • Add operations: InlineList32 is 3-5x faster than List for small collections (zero allocations)
  • Indexer access: Near-identical performance to List (both use direct memory access)
  • Memory: Zero heap allocations vs one allocation for List

See docs/benchmarks.md for full results.

What InlineCollections is

  • Optimized for hot paths in high-performance systems
  • Zero-allocation primitive for small collections
  • Reference-type performance with stack allocation semantics
  • Deterministic and cache-friendly

What InlineCollections is NOT

  • A replacement for List<T>, Stack<T>, or Queue<T>
  • A thread-safe or concurrent collection
  • A general-purpose data structure for arbitrary workloads
  • A garbage-collected or reference-type collection

Trade-offs

Inline vs heap

Inline (InlineCollections):

  • โœ… No allocation
  • โœ… Cache-friendly
  • โœ… Fast small operations
  • โŒ Fixed capacity
  • โŒ Struct copying cost
  • โŒ Stack size cost

Heap (List):

  • โœ… Dynamic capacity
  • โœ… Unbounded growth
  • โœ… No copying (reference type)
  • โŒ Allocation overhead
  • โŒ GC pressure
  • โŒ Cache misses

Unsafe optimization

Bounds-checked (List):

  • โœ… Safe by default
  • โŒ Branch misprediction overhead

Unchecked (InlineCollections):

  • โœ… 0 branches in hot loops
  • โœ… Faster indexing
  • โŒ Caller must ensure valid indices (in practice, usually guaranteed)

About

High-performance inline data structures for .NET with zero/low allocations, designed for hot paths and systems-level workloads.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages