code wiki / (root) / bloom.nx

bloom.nx source

↩ module page · 123 lines · 4455 B

1// bloom.nx -- Bloom filter (Burton Howard Bloom, 1970). 2// 3// Probabilistic set membership: "probably present" or 4// "definitely absent". Uses k independent hash functions to 5// set k bits per insert; a query returns "absent" iff any of 6// k bits is unset. 7// 8// Used for: cache-miss avoidance (Bigtable, Cassandra), spell- 9// checkers, network protocol scoping (BitTorrent peer exchange), 10// password leak detection (HIBP k-anonymity prefix). 11// 12// Trade-off: memory + time efficient at the cost of allowing 13// false positives (no false negatives). For target false- 14// positive rate p, expected items n: 15// m = -n * ln(p) / (ln(2)^2) bits of bitmap 16// k = (m / n) * ln(2) hash functions 17// Caller picks (m, k) at construction. 18// 19// Invariants: 20// BL1 False NEGATIVE rate is exactly 0. If a key was 21// inserted, query returns true. 22// BL2 False positive rate depends on (m, k, n) per the 23// formula above. Caller responsibility. 24// BL3 Capacity (cap_bits) must be a power of two so we can 25// mask instead of modulo. Hash bits map to bit indices 26// via & (cap_bits - 1). 27// BL4 Two hash functions are mixed via the "Kirsch-Mitzenmacher 28// double hashing" trick (h1 + i*h2 for i = 0..k-1) to 29// cheaply produce k independent-enough hashes from two. 30 31import "syscalls.nx" 32import "murmur3.nx" 33 34struct Bloom { 35 bits: *u8, 36 cap: i64, // bitmap size in bits (power of 2) 37 mask: i64, // cap - 1, for modulo-by-mask 38 k: i64, // number of hash functions 39} 40 41func bl_is_pow2(n: i64) -> i64 { 42 if n < 8 { return 0 } 43 if (n & (n - 1)) != 0 { return 0 } 44 return 1 45} 46 47// Allocate a Bloom filter with `cap_bits` bitmap (must be power 48// of 2, >= 8) and k hash functions per insert/query. 49func bloom_new(cap_bits: i64, k: i64) -> *Bloom { 50 if bl_is_pow2(cap_bits) != 1 { return 0 as *Bloom } 51 if k < 1 { return 0 as *Bloom } 52 let bf_raw: *u8 = sys_mmap(64) 53 let bf: *Bloom = bf_raw as *Bloom 54 bf.bits = sys_mmap(cap_bits / 8) 55 var i: i64 = 0 56 while i < cap_bits / 8 { bf.bits[i] = 0; i = i + 1 } 57 bf.cap = cap_bits 58 bf.mask = cap_bits - 1 59 bf.k = k 60 return bf 61} 62 63// Set bit at position `bit_idx`. 64func bl_set_bit(bf: *Bloom, bit_idx: i64) -> i64 { 65 let byte_idx: i64 = (bit_idx >> 3) & ((bf.cap >> 3) - 1) 66 let bit_pos: i64 = bit_idx & 7 67 bf.bits[byte_idx] = bf.bits[byte_idx] | (1 << bit_pos) 68 return 0 69} 70 71// Test bit at position. Returns 0 or 1. 72func bl_get_bit(bf: *Bloom, bit_idx: i64) -> i64 { 73 let byte_idx: i64 = (bit_idx >> 3) & ((bf.cap >> 3) - 1) 74 let bit_pos: i64 = bit_idx & 7 75 return (bf.bits[byte_idx] >> bit_pos) & 1 76} 77 78// Insert a key. Sets k bits derived from two-hash double-hashing. 79func bloom_insert(bf: *Bloom, key: *u8, key_len: i64) -> i64 { 80 let h1: i64 = murmur3_32(0, key, key_len) 81 let h2: i64 = murmur3_32(1, key, key_len) 82 var i: i64 = 0 83 while i < bf.k { 84 let combined: i64 = (h1 + (i * h2)) & 0xFFFFFFFF 85 let bit_idx: i64 = combined & bf.mask 86 bl_set_bit(bf, bit_idx) 87 i = i + 1 88 } 89 return 0 90} 91 92// Query a key. Returns 1 if all k bits are set ("probably 93// present"); 0 if any bit is unset ("definitely absent"). 94func bloom_contains(bf: *Bloom, key: *u8, key_len: i64) -> i64 { 95 let h1: i64 = murmur3_32(0, key, key_len) 96 let h2: i64 = murmur3_32(1, key, key_len) 97 var i: i64 = 0 98 while i < bf.k { 99 let combined: i64 = (h1 + (i * h2)) & 0xFFFFFFFF 100 let bit_idx: i64 = combined & bf.mask 101 if bl_get_bit(bf, bit_idx) == 0 { return 0 } 102 i = i + 1 103 } 104 return 1 105} 106 107// Compile-only smoke. 108func main() -> i64 { 109 let bf: *Bloom = bloom_new(1024, 4) 110 if bf == (0 as *Bloom) { return 1 } 111 bloom_insert(bf, "alice", 5) 112 bloom_insert(bf, "bob", 3) 113 if bloom_contains(bf, "alice", 5) != 1 { return 2 } 114 if bloom_contains(bf, "bob", 3) != 1 { return 3 } 115 // "carol" should be ABSENT (not inserted). 116 if bloom_contains(bf, "carol", 5) != 0 { 117 // could be a false positive at 1024 bits with 2 inserts 118 // -- vanishingly unlikely but technically allowed. Only 119 // fail if it consistently returns true for many absent 120 // keys (smoke is a single check; tolerate). 121 } 122 return 0 123}