Data-IntervalTree-Shared
view release on metacpan or search on metacpan
lib/Data/IntervalTree/Shared.pm view on Meta::CPAN
package Data::IntervalTree::Shared;
use strict;
use warnings;
our $VERSION = '0.02';
require XSLoader;
XSLoader::load('Data::IntervalTree::Shared', $VERSION);
sub CLONE_SKIP { 1 } # blessed C-pointer handle: never clone into ithreads (double-free)
1;
__END__
=encoding utf-8
=head1 NAME
Data::IntervalTree::Shared - shared-memory interval tree (overlap / stabbing queries)
=head1 SYNOPSIS
use Data::IntervalTree::Shared;
# up to 100_000 intervals
my $it = Data::IntervalTree::Shared->new(undef, 100_000);
$it->add($start, $end, $booking_id) for @bookings; # each interval carries an id
# which intervals contain a point?
my @at = $it->stab($t); # e.g. "what's booked at time $t"
# which intervals overlap a range?
my @ov = $it->overlaps($lo, $hi); # e.g. "any booking touching [$lo,$hi]"
printf "id %d: [%d, %d]\n", $_->{id}, $_->{lo}, $_->{hi} for @ov;
# share the index across processes via a backing file
my $shared = Data::IntervalTree::Shared->new("/tmp/bookings.it", 100_000);
=head1 DESCRIPTION
An B<interval tree> in shared memory: a set of integer intervals C<[lo, hi]> that
answers B<overlap> and B<stabbing> queries far faster than scanning every
interval -- "which stored intervals contain point C<p>?" and "which overlap the
range C<[lo, hi]>?". It complements L<Data::SegmentTree::Shared> (range-aggregate
over indexed positions) and L<Data::KDTree::Shared> (multi-dimensional points):
this one indexes a B<set of intervals> for containment/overlap. Classic uses:
scheduling and calendar conflict detection, IP-range to owner lookup, genomic
feature overlap, and "what is active at time C<t>".
Endpoints are B<signed 64-bit integers> (timestamps, IP addresses, genomic
coordinates -- exact, with no floating-point edge cases). Each interval carries a
user-supplied 64-bit B<id> (defaulting to its insertion index), returned with
every match. Internally it is an B<augmented balanced binary search tree> keyed
by the low endpoint, each node caching the maximum high endpoint of its subtree
so a query prunes whole subtrees that end before it.
Intervals are B<appended in O(1)> and the balanced tree is B<bulk-built on the
first query> after any insert, so query recursion is O(log n + k) deep (k =
matches) regardless of insertion order -- no risk of a degenerate, deep tree.
Because the intervals live in a shared mapping, B<several processes build and
query one index>: any process that opens the same backing file, inherits the
anonymous mapping across C<fork>, or reopens a passed memfd sees the same
intervals. A write-preferring futex rwlock with dead-process recovery guards
mutation; once the tree is built, queries take only the read lock. B<Linux-only>.
Requires 64-bit Perl.
The index has a fixed B<capacity>; adding beyond it croaks. Memory is
C<capacity * 40> bytes for the intervals plus a build scratch of C<capacity * 4>
bytes and a fixed header.
=head1 METHODS
=head2 Constructors
my $it = Data::IntervalTree::Shared->new($path, $capacity, $mode);
my $it = Data::IntervalTree::Shared->new(undef, $capacity);
my $it = Data::IntervalTree::Shared->new_memfd($name, $capacity);
my $it = Data::IntervalTree::Shared->new_from_fd($fd);
my $ro = Data::IntervalTree::Shared->new_readonly($path); # frozen file, read-only
C<$capacity> is the maximum number of intervals (1..2^24). C<new> and
C<new_memfd> croak on an out-of-range C<$capacity>. When reopening an existing
file or memfd the stored geometry wins and the caller's argument does not
resize it, though it is still range-checked and an out-of-range value croaks;
reopening a B<sealed> (frozen) file read-write is refused -- use
C<new_readonly> instead (see L</"FROZEN (READ-ONLY) MODE">). An optional file
B<mode> may be passed as the last argument to C<new> (e.g. C<0660>)
for cross-user sharing; it defaults to C<0600> (owner-only).
=head2 Adding intervals
my $i = $it->add($lo, $hi); # id defaults to the insertion index
my $i = $it->add($lo, $hi, $id); # attach an explicit 64-bit id
$it->build; # (optional) force a rebuild now
C<add> appends one interval with integer endpoints C<$lo E<lt>= $hi> (croaks
otherwise) and an optional integer C<$id>, returning its insertion index; it
croaks if the tree is full. Intervals are treated as B<closed> (both endpoints
inclusive). C<build> forces the balanced tree to be (re)built immediately; you
rarely need it, since queries build automatically after inserts.
=head2 Queries
my @at = $it->stab($point); # intervals containing $point (lo <= p <= hi)
my @ov = $it->overlaps($lo, $hi); # intervals overlapping [$lo, $hi]
C<stab> returns every stored interval that B<contains> the point C<$point>.
( run in 1.201 second using v1.01-cache-2.11-cpan-64ef6c95b5d )