Data-Pool-Shared

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN


  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 )