Data-Pool-Shared
view release on metacpan or search on metacpan
Zero-Copy Access
my $sv = $pool->slot_sv($idx); # SV backed by slot memory
Returns a read-only scalar whose PV points directly into the shared
memory slot. Reading the scalar reads the slot with no "memcpy". Useful
for large slots where avoiding copy matters.
The scalar holds a reference to the pool object, keeping it alive for as
long as the scalar (or any copy of it) is live. However, the scalar
still reflects the current contents of the slot: if the slot is free()d
and later re-allocated, reads will see the new data. To modify the slot,
use set().
Status
my $ok = $pool->is_allocated($idx);
my $cap = $pool->capacity;
my $esz = $pool->elem_size;
my $n = $pool->used; # allocated count
my $n = $pool->available; # free count
my $pid = $pool->owner($idx); # PID of allocator
Recovery
my $n = $pool->recover_stale; # free slots owned by dead PIDs
$pool->reset; # free all slots (exclusive access only)
Guards
my ($idx, $guard) = $pool->alloc_guard; # auto-free on scope exit
my ($idx, $guard) = $pool->alloc_guard($timeout);
my ($idx, $guard) = $pool->try_alloc_guard; # non-blocking
Convenience
my $idx = $pool->alloc_set($val); # alloc + set
my $idx = $pool->alloc_set($val, $timeout); # with timeout
my $idx = $pool->try_alloc_set($val); # non-blocking
$pool->each_allocated(sub { my $idx = shift; ... });
Common Methods
my $p = $pool->path; # backing file (undef if anon)
my $fd = $pool->memfd; # memfd fd (-1 if not memfd)
$pool->sync; # msync to disk
$pool->unlink; # remove backing file
my $s = $pool->stats; # diagnostic hashref
eventfd Integration
my $fd = $pool->eventfd; # create eventfd
$pool->eventfd_set($fd); # use existing fd
my $fd = $pool->fileno; # current eventfd (-1 if none)
$pool->notify; # signal eventfd
my $n = $pool->eventfd_consume; # drain counter
STATS
stats() returns a hashref with diagnostic counters. All values are
approximate under concurrency.
"capacity" â total slot count (immutable)
"elem_size" â bytes per slot (immutable)
"used" â currently allocated slot count
"available" â currently free slot count ("capacity - used")
"waiters" â processes currently blocked on "alloc"
"mmap_size" â total mmap region size in bytes
"allocs" â cumulative successful allocations
"frees" â cumulative frees (including stale recovery)
"waits" â "alloc" calls that entered the retry loop
"timeouts" â "alloc" calls that timed out
"recoveries" â slots freed by "recover_stale"
SECURITY
The shared memory region (mmap) is writable by all processes that open
it. A malicious process with write access to the backing file or memfd
can corrupt header fields (bitmap, counters, slot data) and cause other
processes to crash, spin, or return incorrect data. Do not share backing
files with untrusted processes. Use anonymous mode or memfd with
restricted fd passing for isolation.
PERFORMANCE
* Allocation scans a bitmap of ceil(capacity/64) words. O(capacity/64)
worst case, O(1) amortized with scan_hint.
* Each allocation is a single CAS on one bitmap word. Under
contention, CAS retries on the same word are ~10ns each.
* When pool is full, "alloc" blocks on a futex (zero CPU). Woken by a
single "FUTEX_WAKE" syscall on "free".
* "free_n" batches N frees into a single "used" decrement and a single
"FUTEX_WAKE" syscall â faster than N individual frees.
* "slot_sv" provides zero-copy access to slot data, avoiding "memcpy"
overhead for large slots.
* Typed variants (I64, I32) use atomic load/store/CAS/add directly on
the mmap'd memory â no locking overhead.
BENCHMARKS
Measured on a single-socket x86_64 Linux system, Perl 5.40.
Single process (1M ops):
I64 alloc + free 3.3M/s
I64 get/set ~10M/s
I64 add/incr ~10M/s
I64 cas 9.8M/s
Str set (48B) ~10M/s
Str get (48B) 7.5M/s
alloc_set + free 1.9M/s
Multi-process (8 workers, 200K ops each, cap=64):
I64 alloc/free 4.7M/s aggregate
I64 alloc/set/get/free 5.1M/s aggregate
I64 atomic add 22.9M/s aggregate
Str alloc/set/get/free 4.9M/s aggregate
Batch (single process, alloc_n + free_n):
batch=1 ~2.3M/s
batch=16 ~400K/s (vs ~200K individual)
batch=64 ~110K/s (vs ~50K individual, 2x gain)
Bottleneck is Perl XS call overhead, not the CAS or futex.
SEE ALSO
( run in 0.657 second using v1.01-cache-2.11-cpan-e1769b4cff6 )