magic-rings-zig
Magic-Rings-Zig
Magic-Rings-Zig is a high-performance ring buffer library for Zig that implements magic ring buffers with advanced struct-of-arrays support. It provides seamless wraparound access without modulo arithmetic and supports both single-field and multi-field ring buffers optimized for columnar data processing.
The library supports Linux, FreeBSD (via memfd_create or shm_open/shm_unlink), and Windows platforms with cross-platform shared memory capabilities for inter-process communication, built on top of shared-memory-zig.
STATUS: Stable Core with Active Development - The core functionality is stable, with ongoing development for new ideas and tracking updates to Zig. Built and tested with Zig 0.15.0-dev, tracking subsequent releases.
Key Features
- Magic Ring Buffers: Eliminates wraparound handling via virtual memory mapping
- Multi-Field Ring Buffers: Struct-of-arrays processing for columnar data
- Custom Headers: Extensible metadata support for application-specific needs
- Cross-Platform: Linux, FreeBSD, and Windows support with shared memory
- Zero-Copy Operations: Direct memory access with CPU cache efficiency
- Type Safety: Compile-time type checking for both elements and headers
How Magic Ring Buffers Work
A typical ring buffer requires modulo arithmetic for every access:
Traditional Ring Buffer:
+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <-- Buffer slots (indices)
+---+---+---+---+---+---+---+---+---+---+
^ ^
| |
HEAD TAIL
The magic ring buffer uses virtual memory mapping to create a seamless view:
Virtual Memory Layout:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
\__________ First Mapping __________/ \_____________Mirror ____________/
While maintaining a single physical memory allocation:
Physical Memory Mapping:
+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---+---+---+---+---+---+---+---+---+---+
\__________ Original Buffer ________/
This allows contiguous access across the buffer boundary without special wraparound handling.
Installation
Create a build.zig.zon file:
.{
.name = "my-project",
.version = "0.0.0",
.dependencies = .{
.magic_rings = .{
.url = "https://github.com/Peter-Barrow/magic-rings-zig/archive/<git-ref-here>.tar.gz",
.hash = "...",
},
},
}
Add to your build.zig:
const magic_rings = b.dependency("magic_rings", .{});
exe.root_module.addImport("magic_rings", magic_rings.module("magic_rings"));
Usage Examples
Basic Magic Ring Buffer
const std = @import("std");
const magic_rings = @import("magic_rings");
// Create a ring buffer for u64 values with custom header
const Header = struct { sample_rate: f64, channels: u32 };
const Ring = magic_rings.MagicRingWithHeader(u64, Header);
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// Create a new ring buffer
var ring = try Ring.create("my_buffer", 1024, allocator);
defer ring.close() catch {};
// Set custom header fields
ring.header.sample_rate = 44100.0;
ring.header.channels = 2;
// Push data
_ = ring.push(42);
_ = ring.push(123);
// Get slices that seamlessly wrap around
const data = ring.sliceFromTail(2); // [42, 123]
// Push bulk data
const values = [_]u64{ 1, 2, 3, 4, 5 };
_ = ring.pushValues(&values);
Multi-Field Ring Buffer (Struct-of-Arrays)
// Define a point structure
const Point = struct {
x: f64,
y: f64,
timestamp: u64,
};
// Create multi-field ring buffer
const MultiRing = magic_rings.MultiMagicRing(Point, struct {});
var multi = try MultiRing.create("points", 1000, allocator);
defer multi.close() catch {};
// Push complete structs (gets decomposed into separate field buffers)
const point = Point{ .x = 1.5, .y = 2.5, .timestamp = 12345 };
_ = multi.push(point);
// Access individual fields efficiently
const x_values = multi.sliceField(.x, 0, 10); // Get 10 x coordinates
const recent_timestamps = multi.sliceFieldToHead(.timestamp, 5); // Last 5 timestamps
// Get synchronized slices across all fields
const recent_data = multi.sliceToHead(5);
// recent_data.x contains last 5 x values
// recent_data.y contains last 5 y values
// recent_data.timestamp contains last 5 timestamps
// Push columnar data efficiently
const columnar_data = MultiRing.Slice{
.x = &[_]f64{ 1.0, 2.0, 3.0 },
.y = &[_]f64{ 4.0, 5.0, 6.0 },
.timestamp = &[_]u64{ 100, 101, 102 },
};
_ = multi.pushSlice(columnar_data);
Shared Memory Between Processes
// Process 1: Create and write
var ring = try Ring.create("/shared_buffer", 1024, null);
_ = ring.push(42);
// Process 2: Open and read
var ring2 = try Ring.open("/shared_buffer", null);
const value = ring2.valueAt(0); // 42
Performance Benefits
Single-Field Buffers
- Zero modulo arithmetic for wraparound access
- Contiguous memory access improves CPU cache performance
- Direct slicing across buffer boundaries without copying
Multi-Field Buffers
- Struct-of-Arrays layout for better cache locality when processing specific fields
- SIMD-friendly memory patterns for vectorized operations
- Reduced memory waste from struct padding and alignment
- Efficient columnar processing for data analysis workloads
Use Cases
- High-frequency data streams (audio processing, sensor data, network packets)
- Inter-process communication with shared circular buffers
- Real-time systems requiring predictable, low-latency access
- Time-series data processing with efficient columnar access
- Logging systems with circular log buffers
- Scientific computing with large datasets requiring efficient field access
Platform Support
| Platform | Shared Memory | Anonymous Memory |
|---|---|---|
| Linux | shm_open / memfd_create |
memfd_create |
| FreeBSD | shm_open |
memfd_create |
| Windows | CreateFileMapping |
CreateFileMapping |
API Reference
MagicRingWithHeader(T, H)
create(name, length, allocator)- Create new ring bufferopen(name, allocator)- Open existing ring bufferclose()- Clean up resourcespush(value)- Add single elementpushValues(slice)- Add multiple elementsslice(start, stop)- Get range with wraparoundsliceFromTail(count)- Get oldest elementssliceToHead(count)- Get newest elementsvalueAt(index)- Get element at logical index
MultiMagicRing(T, H)
- All single-field operations plus:
sliceField(field, start, stop)- Access specific fieldpushField(field, value)- Push to specific fieldpush(struct_value)- Push complete struct (decomposed)pushSlice(columnar_data)- Efficient bulk columnar insert
Contributing
Contributions are welcome! Please ensure:
- Code follows Zig style conventions
- Tests pass on all supported platforms
- Documentation is updated for new features
- Performance-critical paths are benchmarked
License
MIT