Modules · Data · 0.09

Frozen

Nested data workers read after a fork without unsharing a page.

Data loaded before a fork is shared with every worker by copy-on-write, until the first read writes to a reference count and the page is copied. Frozen is an immutable, position-independent block of bytes holding nested data, read without touching a reference count.

use Frozen;

# once, when deploying: nested data into a block of bytes
Frozen->freeze_to('catalogue.frz', {
    messages => { welcome => 'Hello', bye => 'Goodbye' },
    limits   => { upload => 1_048_576 },
}, flat => '.');

# before forking: every worker reads it without unsharing a page
my $fz = Frozen->open('catalogue.frz');
my ($greeting) = $fz->get('messages.welcome');
print "$greeting\n";                       # Hello

What you get

  • Shared after a fork, really

    Reading a Perl hash writes to its reference counts, so the copy-on-write pages a worker reads become its own. A Frozen block is read by offset and never written.

  • Smaller

    In its POD's measurement the same data takes 6.4 MB as a block against 13.9 MB as a Perl hash: 2.16 times smaller.

  • Flat as it grows

    Lookup stays near flat as a block grows, where a hash slows with size. get('a.b.c') is one probe into a flat index built with flat => '.'.

  • Two doors

    The fast door (get, fetch, child) returns an empty list for an absent key, since undef is a value. The tied door is an ordinary-looking hashref, and slower.

In use

use Frozen;

Frozen->freeze_to('settings.frz', { db => { host => 'db1', port => 5432 } });
my $fz = Frozen->open('settings.frz');

# the fast door: a value, or an empty list when the key is absent
my $db = $fz->child($fz->root, 'db');
my ($port) = $fz->fetch($db, 'port');
my @none   = $fz->fetch($db, 'user');
print "$port, ", scalar(@none), " values for user\n";   # 5432, 0 values for user

# the tied door: an ordinary-looking hashref, slower
my $h = $fz->tied;
print "$h->{db}{host}\n";                               # db1

Measured in its POD

Per hit, best of three runs on one machine, as the POD records it. At 500 keys a Perl hash takes 22.2 ns and Frozen 23.9 ns. At 200,000 keys the hash takes 47.4 ns and Frozen 32.1 ns. For a three-level catalogue, $fz->get('a.b.c') takes 61.4 ns against 88.9 ns for the nested Perl traversal, and the tied door 729.5 ns.

For C

fz_abi.h, resolved at run time through Frozen::_abi_ptr, lets another module's XS read a block with no Perl frame at all, including a block that lives in shared memory and outlives the reader.

Get it

cpanm Frozen