File Coverage

lib/Net/BART/BitSet256.pm
Criterion Covered Total %
statement 59 68 86.7
branch 26 38 68.4
condition n/a
subroutine 14 15 93.3
pod 0 11 0.0
total 99 132 75.0


line stmt bran cond sub pod time code
1             package Net::BART::BitSet256;
2              
3 3     3   85623 use strict;
  3         4  
  3         76  
4 3     3   11 use warnings;
  3         7  
  3         2893  
5              
6             our $VERSION = '0.01';
7              
8             # A 256-bit bitset stored as 4 x 64-bit unsigned integers.
9             # Bit layout: word 0 = bits 0-63, word 1 = bits 64-127,
10             # word 2 = bits 128-191, word 3 = bits 192-255.
11              
12             # Precomputed byte popcount table (0..255)
13             my @BYTE_POPCNT;
14             for my $i (0 .. 255) {
15             my $c = 0;
16             my $v = $i;
17             while ($v) { $v &= ($v - 1); $c++ }
18             $BYTE_POPCNT[$i] = $c;
19             }
20              
21             # Precomputed bit_len for bytes (0..255)
22             my @BYTE_LEN;
23             $BYTE_LEN[0] = 0;
24             for my $i (1 .. 255) {
25             $BYTE_LEN[$i] = $BYTE_LEN[$i >> 1] + 1;
26             }
27              
28             sub new {
29 348     348 0 152905 return bless [0, 0, 0, 0], $_[0];
30             }
31              
32             sub clone {
33 0     0 0 0 return bless [@{$_[0]}], ref($_[0]);
  0         0  
34             }
35              
36             sub set {
37 2122     2122 0 5879 $_[0]->[$_[1] >> 6] |= (1 << ($_[1] & 63));
38             }
39              
40             sub clear {
41 4     4 0 47 $_[0]->[$_[1] >> 6] &= ~(1 << ($_[1] & 63));
42             }
43              
44             sub test {
45 11 100   11 0 97 return ($_[0]->[$_[1] >> 6] & (1 << ($_[1] & 63))) ? 1 : 0;
46             }
47              
48             sub is_empty {
49 3     3 0 21 return !($_[0]->[0] | $_[0]->[1] | $_[0]->[2] | $_[0]->[3]);
50             }
51              
52             # Fast popcount using byte lookup table
53             sub _popcount64 {
54 8     8   13 my $x = $_[0];
55 8         42 return $BYTE_POPCNT[ $x & 0xFF] +
56             $BYTE_POPCNT[($x >> 8) & 0xFF] +
57             $BYTE_POPCNT[($x >> 16) & 0xFF] +
58             $BYTE_POPCNT[($x >> 24) & 0xFF] +
59             $BYTE_POPCNT[($x >> 32) & 0xFF] +
60             $BYTE_POPCNT[($x >> 40) & 0xFF] +
61             $BYTE_POPCNT[($x >> 48) & 0xFF] +
62             $BYTE_POPCNT[($x >> 56) & 0xFF];
63             }
64              
65             # Rank: count of set bits in positions 0..idx (inclusive).
66             # Fully inlined for speed - this is the hottest function.
67             sub rank {
68 905     905 0 1339 my $self = $_[0];
69 905         1225 my $idx = $_[1];
70 905         1267 my $word = $idx >> 6;
71 905         2273 my $bit = $idx & 63;
72              
73 905         1163 my $count = 0;
74              
75             # Unrolled loop for words before target
76 905 100       1734 if ($word > 0) {
77 207         350 my $x = $self->[0];
78 207         663 $count += $BYTE_POPCNT[ $x & 0xFF] + $BYTE_POPCNT[($x >> 8) & 0xFF] +
79             $BYTE_POPCNT[($x >> 16) & 0xFF] + $BYTE_POPCNT[($x >> 24) & 0xFF] +
80             $BYTE_POPCNT[($x >> 32) & 0xFF] + $BYTE_POPCNT[($x >> 40) & 0xFF] +
81             $BYTE_POPCNT[($x >> 48) & 0xFF] + $BYTE_POPCNT[($x >> 56) & 0xFF];
82 207 100       462 if ($word > 1) {
83 140         214 $x = $self->[1];
84 140         378 $count += $BYTE_POPCNT[ $x & 0xFF] + $BYTE_POPCNT[($x >> 8) & 0xFF] +
85             $BYTE_POPCNT[($x >> 16) & 0xFF] + $BYTE_POPCNT[($x >> 24) & 0xFF] +
86             $BYTE_POPCNT[($x >> 32) & 0xFF] + $BYTE_POPCNT[($x >> 40) & 0xFF] +
87             $BYTE_POPCNT[($x >> 48) & 0xFF] + $BYTE_POPCNT[($x >> 56) & 0xFF];
88 140 100       299 if ($word > 2) {
89 70         112 $x = $self->[2];
90 70         265 $count += $BYTE_POPCNT[ $x & 0xFF] + $BYTE_POPCNT[($x >> 8) & 0xFF] +
91             $BYTE_POPCNT[($x >> 16) & 0xFF] + $BYTE_POPCNT[($x >> 24) & 0xFF] +
92             $BYTE_POPCNT[($x >> 32) & 0xFF] + $BYTE_POPCNT[($x >> 40) & 0xFF] +
93             $BYTE_POPCNT[($x >> 48) & 0xFF] + $BYTE_POPCNT[($x >> 56) & 0xFF];
94             }
95             }
96             }
97              
98             # Masked popcount of the target word
99 905 100       1942 my $masked = ($bit == 63) ? $self->[$word] : ($self->[$word] & ((1 << ($bit + 1)) - 1));
100 905         2683 $count += $BYTE_POPCNT[ $masked & 0xFF] + $BYTE_POPCNT[($masked >> 8) & 0xFF] +
101             $BYTE_POPCNT[($masked >> 16) & 0xFF] + $BYTE_POPCNT[($masked >> 24) & 0xFF] +
102             $BYTE_POPCNT[($masked >> 32) & 0xFF] + $BYTE_POPCNT[($masked >> 40) & 0xFF] +
103             $BYTE_POPCNT[($masked >> 48) & 0xFF] + $BYTE_POPCNT[($masked >> 56) & 0xFF];
104              
105 905         2491 return $count;
106             }
107              
108             # IntersectionTop: find the highest set bit in (self AND other).
109             # Returns the bit index, or -1 if the intersection is empty.
110             sub intersection_top {
111 2     2 0 13 my ($self, $other) = @_;
112 2         4 my $w;
113              
114 2 50       5 $w = $self->[3] & $other->[3]; if ($w) { return 192 + _bit_len64($w) - 1 }
  2         6  
  0         0  
115 2 50       5 $w = $self->[2] & $other->[2]; if ($w) { return 128 + _bit_len64($w) - 1 }
  2         7  
  0         0  
116 2 50       5 $w = $self->[1] & $other->[1]; if ($w) { return 64 + _bit_len64($w) - 1 }
  2         4  
  0         0  
117 2 100       5 $w = $self->[0] & $other->[0]; if ($w) { return _bit_len64($w) - 1 }
  2         5  
  1         5  
118 1         6 return -1;
119             }
120              
121             # Intersects: returns true if (self AND other) is non-empty
122             sub intersects {
123 3 100   3 0 27 return (($_[0]->[0] & $_[1]->[0]) |
124             ($_[0]->[1] & $_[1]->[1]) |
125             ($_[0]->[2] & $_[1]->[2]) |
126             ($_[0]->[3] & $_[1]->[3])) ? 1 : 0;
127             }
128              
129             # Number of bits needed to represent x (bits.Len64 equivalent).
130             # Uses byte lookup table for speed.
131             sub _bit_len64 {
132 17     17   25 my $x = $_[0];
133 17 50       40 return 0 unless $x;
134 17 50       39 if ($x >> 32) {
135 0         0 my $hi = $x >> 32;
136 0 0       0 if ($hi >> 16) {
137 0 0       0 return ($hi >> 24) ? 56 + $BYTE_LEN[$hi >> 24] : 48 + $BYTE_LEN[$hi >> 16];
138             }
139 0 0       0 return ($hi >> 8) ? 40 + $BYTE_LEN[$hi >> 8] : 32 + $BYTE_LEN[$hi];
140             }
141 17 100       34 if ($x >> 16) {
142 2 50       15 return ($x >> 24) ? 24 + $BYTE_LEN[$x >> 24] : 16 + $BYTE_LEN[($x >> 16) & 0xFF];
143             }
144 15 100       75 return ($x >> 8) ? 8 + $BYTE_LEN[($x >> 8) & 0xFF] : $BYTE_LEN[$x & 0xFF];
145             }
146              
147             # Iterate over set bits, calling $callback->($bit) for each
148             sub each_set_bit {
149 1     1 0 11 my ($self, $callback) = @_;
150 1         12 for my $i (0, 1, 2, 3) {
151 4         7 my $w = $self->[$i];
152 4 100       10 next unless $w;
153 3         5 my $base = $i << 6;
154 3         6 while ($w) {
155 3         5 my $t = $w & (-$w); # isolate lowest set bit
156 3         7 $callback->($base + _bit_len64($t) - 1);
157 3         14 $w &= ($w - 1);
158             }
159             }
160             }
161              
162             # Total number of set bits
163             sub popcnt {
164 2     2 0 13 return _popcount64($_[0]->[0]) + _popcount64($_[0]->[1]) +
165             _popcount64($_[0]->[2]) + _popcount64($_[0]->[3]);
166             }
167              
168             1;