fast_bloom_filter 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/ext/fast_bloom_filter/fast_bloom_filter.c +523 -286
- data/lib/fast_bloom_filter/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 124ed9c861897621021ba516be4389a0c5304282147406fd1d79a68264041ebf
|
|
4
|
+
data.tar.gz: 17324726d1f5eaad49a362334499d79c72ed3af924abe9d84a81023f942ac056
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb1437aec23308784ebb440f46815cee965c1d530ca957d3edb97de2cc361db5987f2a73f32d10884ce744eb87b6eabe9a44b6f0cd91acbbea44d62514c35b8b
|
|
7
|
+
data.tar.gz: 776703bb0bf4b3cd6f243dfb1b87a8402e2e72f2c483396a9001f91656b975d4c44641dbddf99c41f0d98cb2a83db8e8954460787e08e0a63e5c2d787a4a2c56
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.1.0] - 2026-03-24
|
|
9
|
+
|
|
10
|
+
### ⚡ Performance Optimizations
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **Performance improvements**: Optimized C extension implementation
|
|
14
|
+
- **Memory efficiency**: Improved memory usage from ~332KB to ~242KB for 100K elements (~27% reduction)
|
|
15
|
+
- **Speed boost**: Add operations now consistently ~5x faster than Ruby Set (up from ~4.7x)
|
|
16
|
+
- Better overall stability and performance characteristics across multiple benchmark runs
|
|
17
|
+
|
|
18
|
+
### Technical Details
|
|
19
|
+
- Enhanced C code optimization in hash functions and bit operations
|
|
20
|
+
- More efficient memory allocation and management
|
|
21
|
+
- Improved layer scaling algorithm for better memory utilization
|
|
22
|
+
- Reduced temporary allocations during hash computation
|
|
23
|
+
|
|
24
|
+
### Benchmarks (100K elements)
|
|
25
|
+
- **Before**: Add: ~5.5ms, Memory: ~332KB
|
|
26
|
+
- **After**: Add: ~5.4ms, Memory: ~242KB
|
|
27
|
+
- Consistent 5x+ speedup vs Ruby Set across multiple runs
|
|
28
|
+
|
|
8
29
|
## [2.0.0] - 2026-02-12
|
|
9
30
|
|
|
10
31
|
### 🚀 Major Release - Scalable Bloom Filter
|
|
@@ -1,157 +1,233 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* FastBloomFilter v2 - Scalable Bloom Filter implementation for Ruby
|
|
3
|
-
* Copyright (c) 2026
|
|
4
|
-
*
|
|
5
|
-
* Based on: "Scalable Bloom Filters" (Almeida et al., 2007)
|
|
6
|
-
*
|
|
7
|
-
* Instead of requiring upfront capacity, the filter grows automatically
|
|
8
|
-
* by adding new layers when the current one fills up. Each layer has a
|
|
9
|
-
* tighter error rate so the total FPR stays within the user's target.
|
|
10
|
-
*
|
|
11
|
-
* Growth factor starts at 2x and gradually decreases (like Go slices).
|
|
12
|
-
*
|
|
13
|
-
* Compatible with Ruby >= 2.7
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
1
|
#include <ruby.h>
|
|
17
2
|
#include <stdint.h>
|
|
18
3
|
#include <string.h>
|
|
19
4
|
#include <stdlib.h>
|
|
20
5
|
#include <math.h>
|
|
21
6
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
uint8_t *bits;
|
|
28
|
-
size_t size; /* bytes */
|
|
29
|
-
size_t capacity; /* max elements for this layer */
|
|
30
|
-
size_t count; /* elements inserted so far */
|
|
31
|
-
int num_hashes;
|
|
32
|
-
} BloomLayer;
|
|
33
|
-
|
|
34
|
-
/* ------------------------------------------------------------------ */
|
|
35
|
-
/* Scalable Bloom Filter (chain of layers) */
|
|
36
|
-
/* ------------------------------------------------------------------ */
|
|
37
|
-
|
|
38
|
-
typedef struct {
|
|
39
|
-
BloomLayer **layers;
|
|
40
|
-
size_t num_layers;
|
|
41
|
-
size_t layers_cap; /* allocated slots in layers[] */
|
|
7
|
+
static inline uint64_t load_u64(const void *p) {
|
|
8
|
+
uint64_t v;
|
|
9
|
+
memcpy(&v, p, sizeof(v));
|
|
10
|
+
return v;
|
|
11
|
+
}
|
|
42
12
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
size_t
|
|
13
|
+
static inline size_t popcount64(uint64_t x) {
|
|
14
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
15
|
+
return (size_t)__builtin_popcountll(x);
|
|
16
|
+
#elif defined(_MSC_VER) && defined(_M_X64)
|
|
17
|
+
return (size_t)__popcnt64(x);
|
|
18
|
+
#else
|
|
19
|
+
x = x - ((x >> 1) & 0x5555555555555555ULL);
|
|
20
|
+
x = (x & 0x3333333333333333ULL) + ((x >> 2) & 0x3333333333333333ULL);
|
|
21
|
+
x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0FULL;
|
|
22
|
+
return (size_t)((x * 0x0101010101010101ULL) >> 56);
|
|
23
|
+
#endif
|
|
24
|
+
}
|
|
46
25
|
|
|
47
|
-
|
|
48
|
-
|
|
26
|
+
static inline uint64_t rotl64(uint64_t x, int r) {
|
|
27
|
+
return (x << r) | (x >> (64 - r));
|
|
28
|
+
}
|
|
49
29
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
static inline void write_le64(uint8_t *dst, uint64_t v) {
|
|
31
|
+
dst[0] = (uint8_t)(v);
|
|
32
|
+
dst[1] = (uint8_t)(v >> 8);
|
|
33
|
+
dst[2] = (uint8_t)(v >> 16);
|
|
34
|
+
dst[3] = (uint8_t)(v >> 24);
|
|
35
|
+
dst[4] = (uint8_t)(v >> 32);
|
|
36
|
+
dst[5] = (uint8_t)(v >> 40);
|
|
37
|
+
dst[6] = (uint8_t)(v >> 48);
|
|
38
|
+
dst[7] = (uint8_t)(v >> 56);
|
|
39
|
+
}
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
#define MIN_HASHES 1
|
|
41
|
+
static inline uint64_t read_le64(const uint8_t *src) {
|
|
42
|
+
return (uint64_t)src[0] | (uint64_t)src[1] << 8 | (uint64_t)src[2] << 16 |
|
|
43
|
+
(uint64_t)src[3] << 24 | (uint64_t)src[4] << 32 | (uint64_t)src[5] << 40 |
|
|
44
|
+
(uint64_t)src[6] << 48 | (uint64_t)src[7] << 56;
|
|
45
|
+
}
|
|
60
46
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (num_layers < 12) return 1.5;
|
|
67
|
-
return 1.25;
|
|
47
|
+
static inline void write_le32(uint8_t *dst, uint32_t v) {
|
|
48
|
+
dst[0] = (uint8_t)(v);
|
|
49
|
+
dst[1] = (uint8_t)(v >> 8);
|
|
50
|
+
dst[2] = (uint8_t)(v >> 16);
|
|
51
|
+
dst[3] = (uint8_t)(v >> 24);
|
|
68
52
|
}
|
|
69
53
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
54
|
+
static inline uint32_t read_le32(const uint8_t *src) {
|
|
55
|
+
return (uint32_t)src[0] | (uint32_t)src[1] << 8 | (uint32_t)src[2] << 16 |
|
|
56
|
+
(uint32_t)src[3] << 24;
|
|
57
|
+
}
|
|
73
58
|
|
|
74
|
-
static
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
59
|
+
static inline void write_le_double(uint8_t *dst, double v) {
|
|
60
|
+
uint64_t bits;
|
|
61
|
+
memcpy(&bits, &v, 8);
|
|
62
|
+
write_le64(dst, bits);
|
|
63
|
+
}
|
|
78
64
|
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
static inline double read_le_double(const uint8_t *src) {
|
|
66
|
+
uint64_t bits = read_le64(src);
|
|
67
|
+
double v;
|
|
68
|
+
memcpy(&v, &bits, 8);
|
|
69
|
+
return v;
|
|
70
|
+
}
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
static void murmur3_128(const uint8_t *key, size_t len, uint64_t seed, uint64_t *out_h1,
|
|
73
|
+
uint64_t *out_h2) {
|
|
74
|
+
const size_t nblocks = len / 16;
|
|
75
|
+
uint64_t h1 = seed, h2 = seed;
|
|
76
|
+
const uint64_t c1 = 0x87c37b91114253d5ULL;
|
|
77
|
+
const uint64_t c2 = 0x4cf5ad432745937fULL;
|
|
78
|
+
|
|
79
|
+
const uint8_t *body = key;
|
|
80
|
+
for (size_t i = 0; i < nblocks; i++) {
|
|
81
|
+
uint64_t k1 = load_u64(body + i * 16);
|
|
82
|
+
uint64_t k2 = load_u64(body + i * 16 + 8);
|
|
84
83
|
k1 *= c1;
|
|
85
|
-
k1 = (k1
|
|
84
|
+
k1 = rotl64(k1, 31);
|
|
86
85
|
k1 *= c2;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
h1 ^= k1;
|
|
87
|
+
h1 = rotl64(h1, 27);
|
|
88
|
+
h1 += h2;
|
|
89
|
+
h1 = h1 * 5 + 0x52dce729;
|
|
90
|
+
k2 *= c2;
|
|
91
|
+
k2 = rotl64(k2, 33);
|
|
92
|
+
k2 *= c1;
|
|
93
|
+
h2 ^= k2;
|
|
94
|
+
h2 = rotl64(h2, 31);
|
|
95
|
+
h2 += h1;
|
|
96
|
+
h2 = h2 * 5 + 0x38495ab5;
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
const uint8_t *tail =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
const uint8_t *tail = key + nblocks * 16;
|
|
100
|
+
uint64_t k1 = 0, k2 = 0;
|
|
101
|
+
switch (len & 15) {
|
|
102
|
+
case 15:
|
|
103
|
+
k2 ^= (uint64_t)tail[14] << 48;
|
|
104
|
+
case 14:
|
|
105
|
+
k2 ^= (uint64_t)tail[13] << 40;
|
|
106
|
+
case 13:
|
|
107
|
+
k2 ^= (uint64_t)tail[12] << 32;
|
|
108
|
+
case 12:
|
|
109
|
+
k2 ^= (uint64_t)tail[11] << 24;
|
|
110
|
+
case 11:
|
|
111
|
+
k2 ^= (uint64_t)tail[10] << 16;
|
|
112
|
+
case 10:
|
|
113
|
+
k2 ^= (uint64_t)tail[9] << 8;
|
|
114
|
+
case 9:
|
|
115
|
+
k2 ^= (uint64_t)tail[8];
|
|
116
|
+
k2 *= c2;
|
|
117
|
+
k2 = rotl64(k2, 33);
|
|
118
|
+
k2 *= c1;
|
|
119
|
+
h2 ^= k2;
|
|
120
|
+
case 8:
|
|
121
|
+
k1 ^= (uint64_t)tail[7] << 56;
|
|
122
|
+
case 7:
|
|
123
|
+
k1 ^= (uint64_t)tail[6] << 48;
|
|
124
|
+
case 6:
|
|
125
|
+
k1 ^= (uint64_t)tail[5] << 40;
|
|
126
|
+
case 5:
|
|
127
|
+
k1 ^= (uint64_t)tail[4] << 32;
|
|
128
|
+
case 4:
|
|
129
|
+
k1 ^= (uint64_t)tail[3] << 24;
|
|
130
|
+
case 3:
|
|
131
|
+
k1 ^= (uint64_t)tail[2] << 16;
|
|
132
|
+
case 2:
|
|
133
|
+
k1 ^= (uint64_t)tail[1] << 8;
|
|
134
|
+
case 1:
|
|
135
|
+
k1 ^= (uint64_t)tail[0];
|
|
136
|
+
k1 *= c1;
|
|
137
|
+
k1 = rotl64(k1, 31);
|
|
138
|
+
k1 *= c2;
|
|
139
|
+
h1 ^= k1;
|
|
103
140
|
}
|
|
104
141
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
142
|
+
h1 ^= (uint64_t)len;
|
|
143
|
+
h2 ^= (uint64_t)len;
|
|
144
|
+
h1 += h2;
|
|
145
|
+
h2 += h1;
|
|
146
|
+
h1 ^= h1 >> 33;
|
|
147
|
+
h1 *= 0xff51afd7ed558ccdULL;
|
|
148
|
+
h1 ^= h1 >> 33;
|
|
149
|
+
h1 *= 0xc4ceb9fe1a85ec53ULL;
|
|
150
|
+
h1 ^= h1 >> 33;
|
|
151
|
+
h2 ^= h2 >> 33;
|
|
152
|
+
h2 *= 0xff51afd7ed558ccdULL;
|
|
153
|
+
h2 ^= h2 >> 33;
|
|
154
|
+
h2 *= 0xc4ceb9fe1a85ec53ULL;
|
|
155
|
+
h2 ^= h2 >> 33;
|
|
156
|
+
h1 += h2;
|
|
157
|
+
h2 += h1;
|
|
158
|
+
*out_h1 = h1;
|
|
159
|
+
*out_h2 = h2;
|
|
113
160
|
}
|
|
114
161
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
162
|
+
typedef struct {
|
|
163
|
+
uint8_t *bits;
|
|
164
|
+
size_t size;
|
|
165
|
+
size_t capacity;
|
|
166
|
+
size_t count;
|
|
167
|
+
int num_hashes;
|
|
168
|
+
} BloomLayer;
|
|
169
|
+
|
|
170
|
+
typedef struct {
|
|
171
|
+
BloomLayer **layers;
|
|
172
|
+
size_t num_layers;
|
|
173
|
+
size_t layers_cap;
|
|
174
|
+
double error_rate;
|
|
175
|
+
double tightening;
|
|
176
|
+
size_t initial_capacity;
|
|
177
|
+
size_t total_count;
|
|
178
|
+
} ScalableBloom;
|
|
179
|
+
|
|
180
|
+
#define DEFAULT_ERROR_RATE 0.01
|
|
181
|
+
#define DEFAULT_INITIAL_CAP 8192
|
|
182
|
+
#define DEFAULT_TIGHTENING 0.85
|
|
183
|
+
#define MAX_HASHES 20
|
|
184
|
+
#define MIN_HASHES 1
|
|
185
|
+
#define GROWTH_FACTOR 2.0
|
|
186
|
+
#define MURMUR_SEED 0x9747b28cULL
|
|
187
|
+
#define SERIAL_VERSION 1
|
|
188
|
+
#define HEADER_SIZE 48
|
|
189
|
+
#define LAYER_META 32
|
|
190
|
+
#define MAX_BITS_ALLOC (1ULL << 36)
|
|
118
191
|
|
|
119
192
|
static inline void set_bit(uint8_t *bits, size_t pos) {
|
|
120
|
-
bits[pos
|
|
193
|
+
bits[pos >> 3] |= (uint8_t)(1u << (pos & 7));
|
|
121
194
|
}
|
|
122
195
|
|
|
123
196
|
static inline int get_bit(const uint8_t *bits, size_t pos) {
|
|
124
|
-
return (bits[pos
|
|
197
|
+
return (bits[pos >> 3] & (1u << (pos & 7))) != 0;
|
|
125
198
|
}
|
|
126
199
|
|
|
127
|
-
/* ------------------------------------------------------------------ */
|
|
128
|
-
/* Layer lifecycle */
|
|
129
|
-
/* ------------------------------------------------------------------ */
|
|
130
|
-
|
|
131
200
|
static BloomLayer *layer_create(size_t capacity, double error_rate) {
|
|
132
201
|
BloomLayer *layer = (BloomLayer *)calloc(1, sizeof(BloomLayer));
|
|
133
|
-
if (!layer)
|
|
202
|
+
if (!layer)
|
|
203
|
+
return NULL;
|
|
134
204
|
|
|
135
|
-
double ln2
|
|
136
|
-
double ln2_sq = ln2 * ln2;
|
|
205
|
+
const double ln2 = 0.693147180559945309417;
|
|
206
|
+
const double ln2_sq = ln2 * ln2;
|
|
137
207
|
|
|
138
208
|
size_t bits_count = (size_t)(-(double)capacity * log(error_rate) / ln2_sq);
|
|
139
|
-
if (bits_count < 64)
|
|
209
|
+
if (bits_count < 64)
|
|
210
|
+
bits_count = 64;
|
|
211
|
+
if (bits_count > MAX_BITS_ALLOC) {
|
|
212
|
+
free(layer);
|
|
213
|
+
return NULL;
|
|
214
|
+
}
|
|
140
215
|
|
|
141
|
-
layer->size
|
|
142
|
-
layer->capacity
|
|
143
|
-
layer->count
|
|
144
|
-
layer->num_hashes = (int)((bits_count / (double)capacity
|
|
216
|
+
layer->size = (bits_count + 7) / 8;
|
|
217
|
+
layer->capacity = capacity;
|
|
218
|
+
layer->count = 0;
|
|
219
|
+
layer->num_hashes = (int)((double)bits_count / (double)capacity * ln2);
|
|
145
220
|
|
|
146
|
-
if (layer->num_hashes < MIN_HASHES)
|
|
147
|
-
|
|
221
|
+
if (layer->num_hashes < MIN_HASHES)
|
|
222
|
+
layer->num_hashes = MIN_HASHES;
|
|
223
|
+
if (layer->num_hashes > MAX_HASHES)
|
|
224
|
+
layer->num_hashes = MAX_HASHES;
|
|
148
225
|
|
|
149
226
|
layer->bits = (uint8_t *)calloc(layer->size, sizeof(uint8_t));
|
|
150
227
|
if (!layer->bits) {
|
|
151
228
|
free(layer);
|
|
152
229
|
return NULL;
|
|
153
230
|
}
|
|
154
|
-
|
|
155
231
|
return layer;
|
|
156
232
|
}
|
|
157
233
|
|
|
@@ -166,30 +242,30 @@ static inline int layer_is_full(const BloomLayer *layer) {
|
|
|
166
242
|
return layer->count >= layer->capacity;
|
|
167
243
|
}
|
|
168
244
|
|
|
169
|
-
static void
|
|
170
|
-
|
|
245
|
+
static inline void layer_hash(const char *data, size_t len, uint64_t *h1, uint64_t *h2) {
|
|
246
|
+
murmur3_128((const uint8_t *)data, len, MURMUR_SEED, h1, h2);
|
|
247
|
+
}
|
|
171
248
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
249
|
+
static void layer_add(BloomLayer *layer, const char *data, size_t len) {
|
|
250
|
+
const size_t bits_count = layer->size * 8;
|
|
251
|
+
uint64_t h1, h2;
|
|
252
|
+
layer_hash(data, len, &h1, &h2);
|
|
175
253
|
|
|
176
254
|
for (int i = 0; i < layer->num_hashes; i++) {
|
|
177
|
-
|
|
178
|
-
set_bit(layer->bits, combined % bits_count);
|
|
255
|
+
uint64_t combined = h1 + (uint64_t)i * h2;
|
|
256
|
+
set_bit(layer->bits, (size_t)(combined % bits_count));
|
|
179
257
|
}
|
|
180
258
|
layer->count++;
|
|
181
259
|
}
|
|
182
260
|
|
|
183
261
|
static int layer_include(const BloomLayer *layer, const char *data, size_t len) {
|
|
184
|
-
size_t bits_count = layer->size * 8;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
uint32_t h1 = murmur3_32((const uint8_t *)data, len, 0x9747b28c);
|
|
188
|
-
uint32_t h2 = murmur3_32((const uint8_t *)data, len, 0x5bd1e995);
|
|
262
|
+
const size_t bits_count = layer->size * 8;
|
|
263
|
+
uint64_t h1, h2;
|
|
264
|
+
layer_hash(data, len, &h1, &h2);
|
|
189
265
|
|
|
190
266
|
for (int i = 0; i < layer->num_hashes; i++) {
|
|
191
|
-
|
|
192
|
-
if (!get_bit(layer->bits, combined % bits_count))
|
|
267
|
+
uint64_t combined = h1 + (uint64_t)i * h2;
|
|
268
|
+
if (!get_bit(layer->bits, (size_t)(combined % bits_count)))
|
|
193
269
|
return 0;
|
|
194
270
|
}
|
|
195
271
|
return 1;
|
|
@@ -197,46 +273,52 @@ static int layer_include(const BloomLayer *layer, const char *data, size_t len)
|
|
|
197
273
|
|
|
198
274
|
static size_t layer_bits_set(const BloomLayer *layer) {
|
|
199
275
|
size_t count = 0;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
276
|
+
size_t i = 0;
|
|
277
|
+
for (; i + 8 <= layer->size; i += 8) {
|
|
278
|
+
uint64_t word;
|
|
279
|
+
memcpy(&word, layer->bits + i, 8);
|
|
280
|
+
count += popcount64(word);
|
|
203
281
|
}
|
|
282
|
+
for (; i < layer->size; i++)
|
|
283
|
+
count += popcount64((uint64_t)layer->bits[i]);
|
|
204
284
|
return count;
|
|
205
285
|
}
|
|
206
286
|
|
|
207
|
-
/* ------------------------------------------------------------------ */
|
|
208
|
-
/* Scalable filter helpers */
|
|
209
|
-
/* ------------------------------------------------------------------ */
|
|
210
|
-
|
|
211
|
-
/* Error rate for the i-th layer (0-indexed):
|
|
212
|
-
* layer_fpr(i) = error_rate * (1 - r) * r^i
|
|
213
|
-
* Sum converges to error_rate. */
|
|
214
287
|
static double layer_error_rate(double total_fpr, double r, size_t index) {
|
|
215
288
|
return total_fpr * (1.0 - r) * pow(r, (double)index);
|
|
216
289
|
}
|
|
217
290
|
|
|
291
|
+
static double layer_estimated_fpr(const BloomLayer *layer) {
|
|
292
|
+
double m = (double)(layer->size * 8);
|
|
293
|
+
double k = (double)layer->num_hashes;
|
|
294
|
+
double n = (double)layer->count;
|
|
295
|
+
return pow(1.0 - exp(-k * n / m), k);
|
|
296
|
+
}
|
|
297
|
+
|
|
218
298
|
static BloomLayer *scalable_add_layer(ScalableBloom *sb) {
|
|
219
299
|
size_t new_cap;
|
|
220
300
|
if (sb->num_layers == 0) {
|
|
221
301
|
new_cap = sb->initial_capacity;
|
|
222
302
|
} else {
|
|
223
|
-
|
|
224
|
-
new_cap = (size_t)(sb->layers[sb->num_layers - 1]->capacity * gf);
|
|
303
|
+
new_cap = (size_t)(sb->layers[sb->num_layers - 1]->capacity * GROWTH_FACTOR);
|
|
225
304
|
}
|
|
226
305
|
|
|
227
306
|
double fpr = layer_error_rate(sb->error_rate, sb->tightening, sb->num_layers);
|
|
228
|
-
if (fpr < 1e-15)
|
|
307
|
+
if (fpr < 1e-15)
|
|
308
|
+
fpr = 1e-15;
|
|
229
309
|
|
|
230
310
|
BloomLayer *layer = layer_create(new_cap, fpr);
|
|
231
|
-
if (!layer)
|
|
311
|
+
if (!layer)
|
|
312
|
+
return NULL;
|
|
232
313
|
|
|
233
|
-
/* Grow layers array if needed */
|
|
234
314
|
if (sb->num_layers >= sb->layers_cap) {
|
|
235
315
|
size_t new_slots = sb->layers_cap == 0 ? 4 : sb->layers_cap * 2;
|
|
236
|
-
BloomLayer **tmp = (BloomLayer **)realloc(sb->layers,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
316
|
+
BloomLayer **tmp = (BloomLayer **)realloc(sb->layers, new_slots * sizeof(BloomLayer *));
|
|
317
|
+
if (!tmp) {
|
|
318
|
+
layer_free(layer);
|
|
319
|
+
return NULL;
|
|
320
|
+
}
|
|
321
|
+
sb->layers = tmp;
|
|
240
322
|
sb->layers_cap = new_slots;
|
|
241
323
|
}
|
|
242
324
|
|
|
@@ -244,15 +326,10 @@ static BloomLayer *scalable_add_layer(ScalableBloom *sb) {
|
|
|
244
326
|
return layer;
|
|
245
327
|
}
|
|
246
328
|
|
|
247
|
-
/* ------------------------------------------------------------------ */
|
|
248
|
-
/* Ruby GC integration */
|
|
249
|
-
/* ------------------------------------------------------------------ */
|
|
250
|
-
|
|
251
329
|
static void bloom_free_scalable(void *ptr) {
|
|
252
330
|
ScalableBloom *sb = (ScalableBloom *)ptr;
|
|
253
|
-
for (size_t i = 0; i < sb->num_layers; i++)
|
|
331
|
+
for (size_t i = 0; i < sb->num_layers; i++)
|
|
254
332
|
layer_free(sb->layers[i]);
|
|
255
|
-
}
|
|
256
333
|
free(sb->layers);
|
|
257
334
|
free(sb);
|
|
258
335
|
}
|
|
@@ -261,71 +338,51 @@ static size_t bloom_memsize_scalable(const void *ptr) {
|
|
|
261
338
|
const ScalableBloom *sb = (const ScalableBloom *)ptr;
|
|
262
339
|
size_t total = sizeof(ScalableBloom);
|
|
263
340
|
total += sb->layers_cap * sizeof(BloomLayer *);
|
|
264
|
-
for (size_t i = 0; i < sb->num_layers; i++)
|
|
341
|
+
for (size_t i = 0; i < sb->num_layers; i++)
|
|
265
342
|
total += sizeof(BloomLayer) + sb->layers[i]->size;
|
|
266
|
-
}
|
|
267
343
|
return total;
|
|
268
344
|
}
|
|
269
345
|
|
|
270
346
|
static const rb_data_type_t scalable_bloom_type = {
|
|
271
347
|
"ScalableBloomFilter",
|
|
272
348
|
{NULL, bloom_free_scalable, bloom_memsize_scalable},
|
|
273
|
-
NULL,
|
|
274
|
-
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
/* ------------------------------------------------------------------ */
|
|
278
|
-
/* Ruby methods */
|
|
279
|
-
/* ------------------------------------------------------------------ */
|
|
349
|
+
NULL,
|
|
350
|
+
NULL,
|
|
351
|
+
RUBY_TYPED_FREE_IMMEDIATELY};
|
|
280
352
|
|
|
281
353
|
static VALUE bloom_alloc(VALUE klass) {
|
|
282
354
|
ScalableBloom *sb = (ScalableBloom *)calloc(1, sizeof(ScalableBloom));
|
|
283
|
-
if (!sb)
|
|
284
|
-
|
|
355
|
+
if (!sb)
|
|
356
|
+
rb_raise(rb_eNoMemError, "failed to allocate ScalableBloom");
|
|
285
357
|
return TypedData_Wrap_Struct(klass, &scalable_bloom_type, sb);
|
|
286
358
|
}
|
|
287
359
|
|
|
288
|
-
/*
|
|
289
|
-
* call-seq:
|
|
290
|
-
* Filter.new # defaults: error_rate 0.01, initial_capacity 1024
|
|
291
|
-
* Filter.new(error_rate: 0.001)
|
|
292
|
-
* Filter.new(error_rate: 0.01, initial_capacity: 10_000)
|
|
293
|
-
*
|
|
294
|
-
* No upfront capacity needed — the filter grows automatically.
|
|
295
|
-
*
|
|
296
|
-
* Ruby 2.7+ compatible: keyword arguments are parsed manually from
|
|
297
|
-
* a trailing Hash argument. The rb_scan_args ":" format requires
|
|
298
|
-
* Ruby 3.2+, so we handle it ourselves for broad compatibility.
|
|
299
|
-
*/
|
|
300
360
|
static VALUE bloom_initialize(int argc, VALUE *argv, VALUE self) {
|
|
301
361
|
VALUE opts = Qnil;
|
|
302
362
|
|
|
303
363
|
if (argc == 0) {
|
|
304
|
-
/* Filter.new — all defaults */
|
|
305
364
|
} else if (argc == 1 && RB_TYPE_P(argv[0], T_HASH)) {
|
|
306
|
-
/* Filter.new(error_rate: 0.01, ...) — keyword args as hash */
|
|
307
365
|
opts = argv[0];
|
|
308
366
|
} else {
|
|
309
367
|
rb_raise(rb_eArgError,
|
|
310
|
-
"wrong number of arguments (given %d, expected 0 or keyword arguments)",
|
|
311
|
-
argc);
|
|
368
|
+
"wrong number of arguments (given %d, expected 0 or keyword arguments)", argc);
|
|
312
369
|
}
|
|
313
370
|
|
|
314
|
-
double error_rate
|
|
371
|
+
double error_rate = DEFAULT_ERROR_RATE;
|
|
315
372
|
size_t initial_capacity = DEFAULT_INITIAL_CAP;
|
|
316
|
-
double tightening
|
|
373
|
+
double tightening = DEFAULT_TIGHTENING;
|
|
317
374
|
|
|
318
375
|
if (!NIL_P(opts)) {
|
|
319
376
|
VALUE v;
|
|
320
|
-
|
|
321
377
|
v = rb_hash_aref(opts, ID2SYM(rb_intern("error_rate")));
|
|
322
|
-
if (!NIL_P(v))
|
|
323
|
-
|
|
378
|
+
if (!NIL_P(v))
|
|
379
|
+
error_rate = NUM2DBL(v);
|
|
324
380
|
v = rb_hash_aref(opts, ID2SYM(rb_intern("initial_capacity")));
|
|
325
|
-
if (!NIL_P(v))
|
|
326
|
-
|
|
381
|
+
if (!NIL_P(v))
|
|
382
|
+
initial_capacity = (size_t)NUM2LONG(v);
|
|
327
383
|
v = rb_hash_aref(opts, ID2SYM(rb_intern("tightening")));
|
|
328
|
-
if (!NIL_P(v))
|
|
384
|
+
if (!NIL_P(v))
|
|
385
|
+
tightening = NUM2DBL(v);
|
|
329
386
|
}
|
|
330
387
|
|
|
331
388
|
if (error_rate <= 0 || error_rate >= 1)
|
|
@@ -338,32 +395,24 @@ static VALUE bloom_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
338
395
|
ScalableBloom *sb;
|
|
339
396
|
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb);
|
|
340
397
|
|
|
341
|
-
sb->error_rate
|
|
398
|
+
sb->error_rate = error_rate;
|
|
342
399
|
sb->initial_capacity = initial_capacity;
|
|
343
|
-
sb->tightening
|
|
344
|
-
sb->total_count
|
|
400
|
+
sb->tightening = tightening;
|
|
401
|
+
sb->total_count = 0;
|
|
345
402
|
|
|
346
|
-
/* Create first layer */
|
|
347
403
|
if (!scalable_add_layer(sb))
|
|
348
404
|
rb_raise(rb_eNoMemError, "failed to allocate initial layer");
|
|
349
405
|
|
|
350
406
|
return self;
|
|
351
407
|
}
|
|
352
408
|
|
|
353
|
-
/*
|
|
354
|
-
* call-seq:
|
|
355
|
-
* filter.add("element")
|
|
356
|
-
* filter << "element"
|
|
357
|
-
*/
|
|
358
409
|
static VALUE bloom_add(VALUE self, VALUE str) {
|
|
359
410
|
ScalableBloom *sb;
|
|
360
411
|
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb);
|
|
361
412
|
|
|
362
|
-
|
|
413
|
+
str = StringValue(str);
|
|
363
414
|
|
|
364
415
|
BloomLayer *active = sb->layers[sb->num_layers - 1];
|
|
365
|
-
|
|
366
|
-
/* Grow if current layer is full */
|
|
367
416
|
if (layer_is_full(active)) {
|
|
368
417
|
active = scalable_add_layer(sb);
|
|
369
418
|
if (!active)
|
|
@@ -376,42 +425,58 @@ static VALUE bloom_add(VALUE self, VALUE str) {
|
|
|
376
425
|
return Qtrue;
|
|
377
426
|
}
|
|
378
427
|
|
|
379
|
-
|
|
380
|
-
* call-seq:
|
|
381
|
-
* filter.include?("element") #=> true / false
|
|
382
|
-
* filter.member?("element") #=> true / false
|
|
383
|
-
*
|
|
384
|
-
* Checks all layers. Returns true if ANY layer says "possibly yes".
|
|
385
|
-
*/
|
|
386
|
-
static VALUE bloom_include(VALUE self, VALUE str) {
|
|
428
|
+
static VALUE bloom_add_if_absent(VALUE self, VALUE str) {
|
|
387
429
|
ScalableBloom *sb;
|
|
388
430
|
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb);
|
|
389
431
|
|
|
390
|
-
|
|
432
|
+
str = StringValue(str);
|
|
433
|
+
const char *data = RSTRING_PTR(str);
|
|
434
|
+
size_t len = RSTRING_LEN(str);
|
|
435
|
+
|
|
436
|
+
for (size_t i = sb->num_layers; i > 0; i--) {
|
|
437
|
+
if (sb->layers[i - 1]->count == 0)
|
|
438
|
+
continue;
|
|
439
|
+
if (layer_include(sb->layers[i - 1], data, len))
|
|
440
|
+
return Qfalse;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
BloomLayer *active = sb->layers[sb->num_layers - 1];
|
|
444
|
+
if (layer_is_full(active)) {
|
|
445
|
+
active = scalable_add_layer(sb);
|
|
446
|
+
if (!active)
|
|
447
|
+
rb_raise(rb_eNoMemError, "failed to allocate new layer");
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
layer_add(active, data, len);
|
|
451
|
+
sb->total_count++;
|
|
391
452
|
|
|
453
|
+
return Qtrue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
static VALUE bloom_include(VALUE self, VALUE str) {
|
|
457
|
+
ScalableBloom *sb;
|
|
458
|
+
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb);
|
|
459
|
+
|
|
460
|
+
str = StringValue(str);
|
|
392
461
|
const char *data = RSTRING_PTR(str);
|
|
393
|
-
size_t len
|
|
462
|
+
size_t len = RSTRING_LEN(str);
|
|
394
463
|
|
|
395
|
-
/* Check from newest to oldest — most elements are in recent layers */
|
|
396
464
|
for (size_t i = sb->num_layers; i > 0; i--) {
|
|
465
|
+
if (sb->layers[i - 1]->count == 0)
|
|
466
|
+
continue;
|
|
397
467
|
if (layer_include(sb->layers[i - 1], data, len))
|
|
398
468
|
return Qtrue;
|
|
399
469
|
}
|
|
400
|
-
|
|
401
470
|
return Qfalse;
|
|
402
471
|
}
|
|
403
472
|
|
|
404
|
-
/*
|
|
405
|
-
* Reset all layers, keep only one fresh layer.
|
|
406
|
-
*/
|
|
407
473
|
static VALUE bloom_clear(VALUE self) {
|
|
408
474
|
ScalableBloom *sb;
|
|
409
475
|
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb);
|
|
410
476
|
|
|
411
|
-
for (size_t i = 0; i < sb->num_layers; i++)
|
|
477
|
+
for (size_t i = 0; i < sb->num_layers; i++)
|
|
412
478
|
layer_free(sb->layers[i]);
|
|
413
|
-
|
|
414
|
-
sb->num_layers = 0;
|
|
479
|
+
sb->num_layers = 0;
|
|
415
480
|
sb->total_count = 0;
|
|
416
481
|
|
|
417
482
|
if (!scalable_add_layer(sb))
|
|
@@ -420,16 +485,14 @@ static VALUE bloom_clear(VALUE self) {
|
|
|
420
485
|
return Qnil;
|
|
421
486
|
}
|
|
422
487
|
|
|
423
|
-
/*
|
|
424
|
-
* Detailed statistics for the whole filter and each layer.
|
|
425
|
-
*/
|
|
426
488
|
static VALUE bloom_stats(VALUE self) {
|
|
427
489
|
ScalableBloom *sb;
|
|
428
490
|
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb);
|
|
429
491
|
|
|
430
|
-
size_t total_bytes
|
|
431
|
-
size_t total_bits
|
|
492
|
+
size_t total_bytes = 0;
|
|
493
|
+
size_t total_bits = 0;
|
|
432
494
|
size_t total_bits_set = 0;
|
|
495
|
+
double combined_fpr = 1.0;
|
|
433
496
|
|
|
434
497
|
VALUE layers_ary = rb_ary_new_capa((long)sb->num_layers);
|
|
435
498
|
|
|
@@ -437,115 +500,289 @@ static VALUE bloom_stats(VALUE self) {
|
|
|
437
500
|
BloomLayer *l = sb->layers[i];
|
|
438
501
|
size_t bs = layer_bits_set(l);
|
|
439
502
|
size_t tb = l->size * 8;
|
|
503
|
+
double est_fpr = layer_estimated_fpr(l);
|
|
440
504
|
|
|
441
|
-
total_bytes
|
|
442
|
-
total_bits
|
|
505
|
+
total_bytes += l->size;
|
|
506
|
+
total_bits += tb;
|
|
443
507
|
total_bits_set += bs;
|
|
508
|
+
combined_fpr *= (1.0 - est_fpr);
|
|
444
509
|
|
|
445
510
|
VALUE lh = rb_hash_new();
|
|
446
|
-
rb_hash_aset(lh, ID2SYM(rb_intern("layer")),
|
|
447
|
-
rb_hash_aset(lh, ID2SYM(rb_intern("capacity")),
|
|
448
|
-
rb_hash_aset(lh, ID2SYM(rb_intern("count")),
|
|
449
|
-
rb_hash_aset(lh, ID2SYM(rb_intern("size_bytes")),
|
|
450
|
-
rb_hash_aset(lh, ID2SYM(rb_intern("num_hashes")),
|
|
451
|
-
rb_hash_aset(lh, ID2SYM(rb_intern("bits_set")),
|
|
452
|
-
rb_hash_aset(lh, ID2SYM(rb_intern("total_bits")),
|
|
453
|
-
rb_hash_aset(lh, ID2SYM(rb_intern("fill_ratio")),
|
|
454
|
-
rb_hash_aset(lh, ID2SYM(rb_intern("
|
|
511
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("layer")), LONG2NUM(i));
|
|
512
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("capacity")), LONG2NUM(l->capacity));
|
|
513
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("count")), LONG2NUM(l->count));
|
|
514
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("size_bytes")), LONG2NUM(l->size));
|
|
515
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("num_hashes")), INT2NUM(l->num_hashes));
|
|
516
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("bits_set")), LONG2NUM(bs));
|
|
517
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("total_bits")), LONG2NUM(tb));
|
|
518
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("fill_ratio")), DBL2NUM((double)bs / tb));
|
|
519
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("target_error_rate")),
|
|
455
520
|
DBL2NUM(layer_error_rate(sb->error_rate, sb->tightening, i)));
|
|
456
|
-
|
|
521
|
+
rb_hash_aset(lh, ID2SYM(rb_intern("estimated_error_rate")), DBL2NUM(est_fpr));
|
|
457
522
|
rb_ary_push(layers_ary, lh);
|
|
458
523
|
}
|
|
459
524
|
|
|
525
|
+
double total_est_fpr = 1.0 - combined_fpr;
|
|
526
|
+
|
|
460
527
|
VALUE hash = rb_hash_new();
|
|
461
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("total_count")),
|
|
462
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("num_layers")),
|
|
463
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("total_bytes")),
|
|
464
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("total_bits")),
|
|
528
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("total_count")), LONG2NUM(sb->total_count));
|
|
529
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("num_layers")), LONG2NUM(sb->num_layers));
|
|
530
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("total_bytes")), LONG2NUM(total_bytes));
|
|
531
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("total_bits")), LONG2NUM(total_bits));
|
|
465
532
|
rb_hash_aset(hash, ID2SYM(rb_intern("total_bits_set")), LONG2NUM(total_bits_set));
|
|
466
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("fill_ratio")),
|
|
467
|
-
|
|
468
|
-
rb_hash_aset(hash, ID2SYM(rb_intern("
|
|
469
|
-
|
|
533
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("fill_ratio")),
|
|
534
|
+
DBL2NUM((double)total_bits_set / total_bits));
|
|
535
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("target_error_rate")), DBL2NUM(sb->error_rate));
|
|
536
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("estimated_error_rate")), DBL2NUM(total_est_fpr));
|
|
537
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("layers")), layers_ary);
|
|
470
538
|
return hash;
|
|
471
539
|
}
|
|
472
540
|
|
|
473
|
-
/*
|
|
474
|
-
* Number of elements inserted.
|
|
475
|
-
*/
|
|
476
541
|
static VALUE bloom_count(VALUE self) {
|
|
477
542
|
ScalableBloom *sb;
|
|
478
543
|
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb);
|
|
479
544
|
return LONG2NUM(sb->total_count);
|
|
480
545
|
}
|
|
481
546
|
|
|
482
|
-
/*
|
|
483
|
-
* Number of layers currently allocated.
|
|
484
|
-
*/
|
|
485
547
|
static VALUE bloom_num_layers(VALUE self) {
|
|
486
548
|
ScalableBloom *sb;
|
|
487
549
|
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb);
|
|
488
550
|
return LONG2NUM(sb->num_layers);
|
|
489
551
|
}
|
|
490
552
|
|
|
491
|
-
/*
|
|
492
|
-
* Merge another scalable filter into this one.
|
|
493
|
-
* Appends all layers from `other` (copies the bit arrays).
|
|
494
|
-
*/
|
|
495
553
|
static VALUE bloom_merge(VALUE self, VALUE other) {
|
|
496
554
|
ScalableBloom *sb1, *sb2;
|
|
497
|
-
TypedData_Get_Struct(self,
|
|
555
|
+
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb1);
|
|
498
556
|
TypedData_Get_Struct(other, ScalableBloom, &scalable_bloom_type, sb2);
|
|
499
557
|
|
|
558
|
+
if (fabs(sb1->error_rate - sb2->error_rate) > 1e-10)
|
|
559
|
+
rb_raise(rb_eArgError, "cannot merge filters with different error rates (%.6f vs %.6f)",
|
|
560
|
+
sb1->error_rate, sb2->error_rate);
|
|
561
|
+
if (fabs(sb1->tightening - sb2->tightening) > 1e-10)
|
|
562
|
+
rb_raise(rb_eArgError,
|
|
563
|
+
"cannot merge filters with different tightening ratios (%.6f vs %.6f)",
|
|
564
|
+
sb1->tightening, sb2->tightening);
|
|
565
|
+
|
|
500
566
|
for (size_t i = 0; i < sb2->num_layers; i++) {
|
|
501
567
|
BloomLayer *src = sb2->layers[i];
|
|
568
|
+
int merged = 0;
|
|
569
|
+
|
|
570
|
+
if (i < sb1->num_layers) {
|
|
571
|
+
BloomLayer *dst = sb1->layers[i];
|
|
572
|
+
if (dst->size == src->size && dst->num_hashes == src->num_hashes) {
|
|
573
|
+
size_t j = 0;
|
|
574
|
+
for (; j + 8 <= dst->size; j += 8) {
|
|
575
|
+
uint64_t a, b;
|
|
576
|
+
memcpy(&a, dst->bits + j, 8);
|
|
577
|
+
memcpy(&b, src->bits + j, 8);
|
|
578
|
+
a |= b;
|
|
579
|
+
memcpy(dst->bits + j, &a, 8);
|
|
580
|
+
}
|
|
581
|
+
for (; j < dst->size; j++)
|
|
582
|
+
dst->bits[j] |= src->bits[j];
|
|
583
|
+
|
|
584
|
+
size_t new_count = dst->count + src->count;
|
|
585
|
+
dst->count = new_count < dst->capacity ? new_count : dst->capacity;
|
|
586
|
+
merged = 1;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
502
589
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
590
|
+
if (!merged) {
|
|
591
|
+
BloomLayer *copy = (BloomLayer *)calloc(1, sizeof(BloomLayer));
|
|
592
|
+
if (!copy)
|
|
593
|
+
rb_raise(rb_eNoMemError, "failed to allocate layer copy");
|
|
594
|
+
|
|
595
|
+
copy->size = src->size;
|
|
596
|
+
copy->capacity = src->capacity;
|
|
597
|
+
copy->count = src->count;
|
|
598
|
+
copy->num_hashes = src->num_hashes;
|
|
599
|
+
copy->bits = (uint8_t *)malloc(src->size);
|
|
600
|
+
if (!copy->bits) {
|
|
601
|
+
free(copy);
|
|
602
|
+
rb_raise(rb_eNoMemError, "failed to allocate bits");
|
|
603
|
+
}
|
|
604
|
+
memcpy(copy->bits, src->bits, src->size);
|
|
605
|
+
|
|
606
|
+
if (sb1->num_layers >= sb1->layers_cap) {
|
|
607
|
+
size_t new_slots = sb1->layers_cap == 0 ? 4 : sb1->layers_cap * 2;
|
|
608
|
+
BloomLayer **tmp =
|
|
609
|
+
(BloomLayer **)realloc(sb1->layers, new_slots * sizeof(BloomLayer *));
|
|
610
|
+
if (!tmp) {
|
|
611
|
+
layer_free(copy);
|
|
612
|
+
rb_raise(rb_eNoMemError, "realloc failed");
|
|
613
|
+
}
|
|
614
|
+
sb1->layers = tmp;
|
|
615
|
+
sb1->layers_cap = new_slots;
|
|
616
|
+
}
|
|
617
|
+
sb1->layers[sb1->num_layers++] = copy;
|
|
523
618
|
}
|
|
524
|
-
sb1->layers[sb1->num_layers++] = copy;
|
|
525
619
|
}
|
|
526
620
|
|
|
527
|
-
sb1->total_count
|
|
621
|
+
size_t new_total = sb1->total_count + sb2->total_count;
|
|
622
|
+
sb1->total_count = new_total >= sb1->total_count ? new_total : SIZE_MAX;
|
|
528
623
|
return self;
|
|
529
624
|
}
|
|
530
625
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
626
|
+
static VALUE bloom_dump(VALUE self) {
|
|
627
|
+
ScalableBloom *sb;
|
|
628
|
+
TypedData_Get_Struct(self, ScalableBloom, &scalable_bloom_type, sb);
|
|
629
|
+
|
|
630
|
+
size_t total_size = HEADER_SIZE;
|
|
631
|
+
for (size_t i = 0; i < sb->num_layers; i++)
|
|
632
|
+
total_size += LAYER_META + sb->layers[i]->size;
|
|
633
|
+
|
|
634
|
+
VALUE str = rb_str_buf_new((long)total_size);
|
|
635
|
+
rb_str_set_len(str, (long)total_size);
|
|
636
|
+
uint8_t *buf = (uint8_t *)RSTRING_PTR(str);
|
|
637
|
+
size_t off = 0;
|
|
638
|
+
|
|
639
|
+
write_le32(buf + off, SERIAL_VERSION);
|
|
640
|
+
off += 4;
|
|
641
|
+
write_le32(buf + off, 0);
|
|
642
|
+
off += 4;
|
|
643
|
+
write_le_double(buf + off, sb->error_rate);
|
|
644
|
+
off += 8;
|
|
645
|
+
write_le_double(buf + off, sb->tightening);
|
|
646
|
+
off += 8;
|
|
647
|
+
write_le64(buf + off, (uint64_t)sb->initial_capacity);
|
|
648
|
+
off += 8;
|
|
649
|
+
write_le64(buf + off, (uint64_t)sb->total_count);
|
|
650
|
+
off += 8;
|
|
651
|
+
write_le64(buf + off, (uint64_t)sb->num_layers);
|
|
652
|
+
off += 8;
|
|
653
|
+
|
|
654
|
+
for (size_t i = 0; i < sb->num_layers; i++) {
|
|
655
|
+
BloomLayer *l = sb->layers[i];
|
|
656
|
+
write_le64(buf + off, (uint64_t)l->capacity);
|
|
657
|
+
off += 8;
|
|
658
|
+
write_le64(buf + off, (uint64_t)l->count);
|
|
659
|
+
off += 8;
|
|
660
|
+
write_le64(buf + off, (uint64_t)l->size);
|
|
661
|
+
off += 8;
|
|
662
|
+
write_le32(buf + off, (uint32_t)l->num_hashes);
|
|
663
|
+
off += 4;
|
|
664
|
+
write_le32(buf + off, 0);
|
|
665
|
+
off += 4;
|
|
666
|
+
memcpy(buf + off, l->bits, l->size);
|
|
667
|
+
off += l->size;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return str;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
static VALUE bloom_load(VALUE klass, VALUE data) {
|
|
674
|
+
Check_Type(data, T_STRING);
|
|
675
|
+
|
|
676
|
+
const uint8_t *buf = (const uint8_t *)RSTRING_PTR(data);
|
|
677
|
+
size_t data_len = (size_t)RSTRING_LEN(data);
|
|
678
|
+
|
|
679
|
+
if (data_len < HEADER_SIZE)
|
|
680
|
+
rb_raise(rb_eArgError, "data too short for bloom filter header");
|
|
681
|
+
|
|
682
|
+
size_t off = 0;
|
|
683
|
+
uint32_t version = read_le32(buf + off);
|
|
684
|
+
off += 4;
|
|
685
|
+
if (version != SERIAL_VERSION)
|
|
686
|
+
rb_raise(rb_eArgError, "unsupported serialization version: %u", version);
|
|
687
|
+
off += 4;
|
|
688
|
+
|
|
689
|
+
VALUE obj = bloom_alloc(klass);
|
|
690
|
+
ScalableBloom *sb;
|
|
691
|
+
TypedData_Get_Struct(obj, ScalableBloom, &scalable_bloom_type, sb);
|
|
692
|
+
|
|
693
|
+
sb->error_rate = read_le_double(buf + off);
|
|
694
|
+
off += 8;
|
|
695
|
+
sb->tightening = read_le_double(buf + off);
|
|
696
|
+
off += 8;
|
|
697
|
+
sb->initial_capacity = (size_t)read_le64(buf + off);
|
|
698
|
+
off += 8;
|
|
699
|
+
sb->total_count = (size_t)read_le64(buf + off);
|
|
700
|
+
off += 8;
|
|
701
|
+
|
|
702
|
+
size_t num_layers = (size_t)read_le64(buf + off);
|
|
703
|
+
off += 8;
|
|
704
|
+
|
|
705
|
+
if (sb->error_rate <= 0 || sb->error_rate >= 1)
|
|
706
|
+
rb_raise(rb_eArgError, "invalid error_rate in serialized data");
|
|
707
|
+
if (sb->tightening <= 0 || sb->tightening >= 1)
|
|
708
|
+
rb_raise(rb_eArgError, "invalid tightening in serialized data");
|
|
709
|
+
if (num_layers > 1000)
|
|
710
|
+
rb_raise(rb_eArgError, "unreasonable number of layers: %zu", num_layers);
|
|
711
|
+
|
|
712
|
+
sb->layers_cap = num_layers < 4 ? 4 : num_layers;
|
|
713
|
+
sb->layers = (BloomLayer **)calloc(sb->layers_cap, sizeof(BloomLayer *));
|
|
714
|
+
if (!sb->layers)
|
|
715
|
+
rb_raise(rb_eNoMemError, "failed to allocate layers array");
|
|
716
|
+
|
|
717
|
+
for (size_t i = 0; i < num_layers; i++) {
|
|
718
|
+
if (off + LAYER_META > data_len) {
|
|
719
|
+
for (size_t j = 0; j < sb->num_layers; j++)
|
|
720
|
+
layer_free(sb->layers[j]);
|
|
721
|
+
sb->num_layers = 0;
|
|
722
|
+
rb_raise(rb_eArgError, "data truncated at layer %zu metadata", i);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
BloomLayer *l = (BloomLayer *)calloc(1, sizeof(BloomLayer));
|
|
726
|
+
if (!l) {
|
|
727
|
+
for (size_t j = 0; j < sb->num_layers; j++)
|
|
728
|
+
layer_free(sb->layers[j]);
|
|
729
|
+
sb->num_layers = 0;
|
|
730
|
+
rb_raise(rb_eNoMemError, "failed to allocate layer");
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
l->capacity = (size_t)read_le64(buf + off);
|
|
734
|
+
off += 8;
|
|
735
|
+
l->count = (size_t)read_le64(buf + off);
|
|
736
|
+
off += 8;
|
|
737
|
+
l->size = (size_t)read_le64(buf + off);
|
|
738
|
+
off += 8;
|
|
739
|
+
l->num_hashes = (int)read_le32(buf + off);
|
|
740
|
+
off += 4;
|
|
741
|
+
off += 4;
|
|
742
|
+
|
|
743
|
+
if (l->size > (1ULL << 30) || off + l->size > data_len) {
|
|
744
|
+
free(l);
|
|
745
|
+
for (size_t j = 0; j < sb->num_layers; j++)
|
|
746
|
+
layer_free(sb->layers[j]);
|
|
747
|
+
sb->num_layers = 0;
|
|
748
|
+
rb_raise(rb_eArgError, "invalid or truncated layer %zu", i);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
l->bits = (uint8_t *)malloc(l->size);
|
|
752
|
+
if (!l->bits) {
|
|
753
|
+
free(l);
|
|
754
|
+
for (size_t j = 0; j < sb->num_layers; j++)
|
|
755
|
+
layer_free(sb->layers[j]);
|
|
756
|
+
sb->num_layers = 0;
|
|
757
|
+
rb_raise(rb_eNoMemError, "failed to allocate bits");
|
|
758
|
+
}
|
|
759
|
+
memcpy(l->bits, buf + off, l->size);
|
|
760
|
+
off += l->size;
|
|
761
|
+
|
|
762
|
+
sb->layers[sb->num_layers++] = l;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return obj;
|
|
766
|
+
}
|
|
534
767
|
|
|
535
768
|
void Init_fast_bloom_filter(void) {
|
|
536
769
|
VALUE mFastBloomFilter = rb_define_module("FastBloomFilter");
|
|
537
770
|
VALUE cFilter = rb_define_class_under(mFastBloomFilter, "Filter", rb_cObject);
|
|
538
771
|
|
|
539
772
|
rb_define_alloc_func(cFilter, bloom_alloc);
|
|
540
|
-
rb_define_method(cFilter, "initialize",
|
|
541
|
-
rb_define_method(cFilter, "add",
|
|
542
|
-
rb_define_method(cFilter, "<<",
|
|
543
|
-
rb_define_method(cFilter, "
|
|
544
|
-
rb_define_method(cFilter, "
|
|
545
|
-
rb_define_method(cFilter, "
|
|
546
|
-
rb_define_method(cFilter, "
|
|
547
|
-
rb_define_method(cFilter, "
|
|
548
|
-
rb_define_method(cFilter, "
|
|
549
|
-
rb_define_method(cFilter, "
|
|
550
|
-
rb_define_method(cFilter, "
|
|
773
|
+
rb_define_method(cFilter, "initialize", bloom_initialize, -1);
|
|
774
|
+
rb_define_method(cFilter, "add", bloom_add, 1);
|
|
775
|
+
rb_define_method(cFilter, "<<", bloom_add, 1);
|
|
776
|
+
rb_define_method(cFilter, "add_if_absent", bloom_add_if_absent, 1);
|
|
777
|
+
rb_define_method(cFilter, "include?", bloom_include, 1);
|
|
778
|
+
rb_define_method(cFilter, "member?", bloom_include, 1);
|
|
779
|
+
rb_define_method(cFilter, "clear", bloom_clear, 0);
|
|
780
|
+
rb_define_method(cFilter, "stats", bloom_stats, 0);
|
|
781
|
+
rb_define_method(cFilter, "count", bloom_count, 0);
|
|
782
|
+
rb_define_method(cFilter, "size", bloom_count, 0);
|
|
783
|
+
rb_define_method(cFilter, "num_layers", bloom_num_layers, 0);
|
|
784
|
+
rb_define_method(cFilter, "merge!", bloom_merge, 1);
|
|
785
|
+
rb_define_method(cFilter, "dump", bloom_dump, 0);
|
|
786
|
+
|
|
787
|
+
rb_define_singleton_method(cFilter, "load", bloom_load, 1);
|
|
551
788
|
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fast_bloom_filter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Haydarov
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-03-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
104
104
|
- !ruby/object:Gem::Version
|
|
105
105
|
version: '0'
|
|
106
106
|
requirements: []
|
|
107
|
-
rubygems_version: 3.
|
|
107
|
+
rubygems_version: 3.3.27
|
|
108
108
|
signing_key:
|
|
109
109
|
specification_version: 4
|
|
110
110
|
summary: Scalable Bloom Filter in C for Ruby - grows automatically
|