fast_bloom_filter 1.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 +75 -0
- data/README.md +138 -48
- data/ext/fast_bloom_filter/fast_bloom_filter.c +733 -216
- data/lib/fast_bloom_filter/version.rb +1 -1
- data/lib/fast_bloom_filter.rb +13 -13
- metadata +12 -12
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,81 @@ 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
|
+
|
|
29
|
+
## [2.0.0] - 2026-02-12
|
|
30
|
+
|
|
31
|
+
### π Major Release - Scalable Bloom Filter
|
|
32
|
+
|
|
33
|
+
This is a **breaking change** that transforms FastBloomFilter into a scalable, dynamic data structure.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- **Scalable Architecture**: Filter now grows automatically by adding layers
|
|
37
|
+
- **No Upfront Capacity**: No need to specify capacity - just set error_rate
|
|
38
|
+
- **Multi-Layer System**: Each layer has progressively tighter error rates
|
|
39
|
+
- **Smart Growth Strategy**: Growth factor starts at 2x and decreases (like Go slices)
|
|
40
|
+
- **Layer Statistics**: Detailed per-layer stats via `stats` method
|
|
41
|
+
- **New API**: `Filter.new(error_rate: 0.01, initial_capacity: 1024)`
|
|
42
|
+
- `num_layers` method to check how many layers are active
|
|
43
|
+
- Enhanced `merge!` to combine filters with all their layers
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- **BREAKING**: Constructor now uses keyword arguments: `Filter.new(error_rate: 0.01)` instead of `Filter.new(capacity, error_rate)`
|
|
47
|
+
- **BREAKING**: `stats` now returns multi-layer information with `:layers` array
|
|
48
|
+
- **BREAKING**: Helper methods changed: `for_emails(error_rate: 0.001)` instead of `for_emails(capacity)`
|
|
49
|
+
- Memory allocation is now dynamic and grows on-demand
|
|
50
|
+
- `inspect` output now shows layer count and total elements
|
|
51
|
+
|
|
52
|
+
### Technical Details
|
|
53
|
+
- Based on "Scalable Bloom Filters" (Almeida et al., 2007)
|
|
54
|
+
- Each layer uses error_rate * (1 - r) * r^i formula
|
|
55
|
+
- Default tightening factor (r) = 0.85
|
|
56
|
+
- Growth factors: 2x β 1.75x β 1.5x β 1.25x as layers increase
|
|
57
|
+
- Layers are checked from newest to oldest for better cache locality
|
|
58
|
+
|
|
59
|
+
### Migration Guide
|
|
60
|
+
|
|
61
|
+
**v1.x code:**
|
|
62
|
+
```ruby
|
|
63
|
+
bloom = FastBloomFilter::Filter.new(10_000, 0.01)
|
|
64
|
+
bloom = FastBloomFilter.for_emails(100_000)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**v2.x code:**
|
|
68
|
+
```ruby
|
|
69
|
+
bloom = FastBloomFilter::Filter.new(error_rate: 0.01, initial_capacity: 1000)
|
|
70
|
+
bloom = FastBloomFilter.for_emails(error_rate: 0.001, initial_capacity: 10_000)
|
|
71
|
+
# Or simply:
|
|
72
|
+
bloom = FastBloomFilter::Filter.new(error_rate: 0.01) # starts small, grows as needed
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Performance
|
|
76
|
+
- Same O(k) complexity for add/lookup
|
|
77
|
+
- Slightly higher memory overhead due to layer management
|
|
78
|
+
- Better memory efficiency for unknown/growing datasets
|
|
79
|
+
- No performance degradation as filter grows
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
8
83
|
## [1.0.0] - 2026-02-09
|
|
9
84
|
|
|
10
85
|
### Added
|
data/README.md
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
# FastBloomFilter
|
|
1
|
+
# FastBloomFilter v2 π
|
|
2
2
|
|
|
3
|
-
[](https://github.com/yourusername/fast_bloom_filter/actions/workflows/ci.yml)
|
|
4
3
|
[](https://badge.fury.io/rb/fast_bloom_filter)
|
|
5
4
|
|
|
6
|
-
A
|
|
5
|
+
A **scalable** Bloom Filter implementation in C for Ruby. Grows automatically without requiring upfront capacity! Perfect for Rails applications that need memory-efficient set membership testing with unknown dataset sizes.
|
|
6
|
+
|
|
7
|
+
## What's New in v2? π
|
|
8
|
+
|
|
9
|
+
- **π Scalable Architecture**: No need to guess capacity upfront - the filter grows automatically
|
|
10
|
+
- **π Multi-Layer System**: Adds new layers dynamically as data grows
|
|
11
|
+
- **π― Smart Growth**: Growth factor adapts (2x β 1.75x β 1.5x β 1.25x)
|
|
12
|
+
- **π‘ Simpler API**: Just specify error rate, not capacity
|
|
13
|
+
- **π Better for Unknown Sizes**: Perfect when you don't know how much data you'll have
|
|
14
|
+
|
|
15
|
+
Based on ["Scalable Bloom Filters" (Almeida et al., 2007)](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=10.1.1.725.390)
|
|
7
16
|
|
|
8
17
|
## Features
|
|
9
18
|
|
|
10
19
|
- **π Fast**: C implementation with MurmurHash3
|
|
11
20
|
- **πΎ Memory Efficient**: 20-50x less memory than Ruby Set
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- **π Statistics**:
|
|
21
|
+
- **π Auto-Scaling**: Grows dynamically as you add elements
|
|
22
|
+
- **π― Configurable**: Adjustable false positive rate per layer
|
|
23
|
+
- **π Statistics**: Detailed per-layer performance monitoring
|
|
15
24
|
- **β
Well-Tested**: Comprehensive test suite
|
|
16
25
|
|
|
17
26
|
## Installation
|
|
@@ -36,15 +45,19 @@ gem install fast_bloom_filter
|
|
|
36
45
|
|
|
37
46
|
## Usage
|
|
38
47
|
|
|
39
|
-
### Basic Operations
|
|
48
|
+
### Basic Operations (v2 API)
|
|
40
49
|
|
|
41
50
|
```ruby
|
|
42
51
|
require 'fast_bloom_filter'
|
|
43
52
|
|
|
44
|
-
# Create a filter
|
|
45
|
-
|
|
53
|
+
# Create a scalable filter - NO CAPACITY NEEDED!
|
|
54
|
+
# Just specify your desired error rate
|
|
55
|
+
bloom = FastBloomFilter::Filter.new(error_rate: 0.01)
|
|
46
56
|
|
|
47
|
-
#
|
|
57
|
+
# Or with an initial capacity hint (optional)
|
|
58
|
+
bloom = FastBloomFilter::Filter.new(error_rate: 0.01, initial_capacity: 1000)
|
|
59
|
+
|
|
60
|
+
# Add items - filter grows automatically
|
|
48
61
|
bloom.add("user@example.com")
|
|
49
62
|
bloom << "another@example.com" # alias for add
|
|
50
63
|
|
|
@@ -52,6 +65,13 @@ bloom << "another@example.com" # alias for add
|
|
|
52
65
|
bloom.include?("user@example.com") # => true
|
|
53
66
|
bloom.include?("notfound@test.com") # => false (probably)
|
|
54
67
|
|
|
68
|
+
# Add thousands or millions - it scales!
|
|
69
|
+
100_000.times { |i| bloom.add("user#{i}@test.com") }
|
|
70
|
+
|
|
71
|
+
# Check stats
|
|
72
|
+
bloom.count # => 100002
|
|
73
|
+
bloom.num_layers # => 8 (grew automatically!)
|
|
74
|
+
|
|
55
75
|
# Batch operations
|
|
56
76
|
emails = ["user1@test.com", "user2@test.com", "user3@test.com"]
|
|
57
77
|
bloom.add_all(emails)
|
|
@@ -67,50 +87,99 @@ bloom.clear
|
|
|
67
87
|
|
|
68
88
|
```ruby
|
|
69
89
|
# For email deduplication (0.1% false positive rate)
|
|
70
|
-
bloom = FastBloomFilter.for_emails(
|
|
90
|
+
bloom = FastBloomFilter.for_emails(error_rate: 0.001)
|
|
71
91
|
|
|
72
92
|
# For URL tracking (1% false positive rate)
|
|
73
|
-
bloom = FastBloomFilter.for_urls(
|
|
93
|
+
bloom = FastBloomFilter.for_urls(error_rate: 0.01)
|
|
94
|
+
|
|
95
|
+
# With initial capacity hint
|
|
96
|
+
bloom = FastBloomFilter.for_emails(error_rate: 0.001, initial_capacity: 10_000)
|
|
74
97
|
```
|
|
75
98
|
|
|
76
99
|
### Merge Filters
|
|
77
100
|
|
|
78
101
|
```ruby
|
|
79
|
-
bloom1 = FastBloomFilter::Filter.new(
|
|
80
|
-
bloom2 = FastBloomFilter::Filter.new(
|
|
102
|
+
bloom1 = FastBloomFilter::Filter.new(error_rate: 0.01)
|
|
103
|
+
bloom2 = FastBloomFilter::Filter.new(error_rate: 0.01)
|
|
81
104
|
|
|
82
105
|
bloom1.add("item1")
|
|
83
106
|
bloom2.add("item2")
|
|
84
107
|
|
|
85
108
|
bloom1.merge!(bloom2) # bloom1 now contains both items
|
|
109
|
+
# Merges all layers from bloom2 into bloom1
|
|
86
110
|
```
|
|
87
111
|
|
|
88
112
|
### Statistics
|
|
89
113
|
|
|
90
114
|
```ruby
|
|
91
|
-
bloom = FastBloomFilter::Filter.new(
|
|
115
|
+
bloom = FastBloomFilter::Filter.new(error_rate: 0.01)
|
|
116
|
+
1000.times { |i| bloom.add("item#{i}") }
|
|
117
|
+
|
|
92
118
|
stats = bloom.stats
|
|
93
119
|
|
|
94
120
|
# => {
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
121
|
+
# total_count: 1000,
|
|
122
|
+
# num_layers: 2,
|
|
123
|
+
# total_bytes: 2500,
|
|
124
|
+
# total_bits: 20000,
|
|
125
|
+
# total_bits_set: 6543,
|
|
126
|
+
# fill_ratio: 0.32715,
|
|
127
|
+
# error_rate: 0.01,
|
|
128
|
+
# layers: [
|
|
129
|
+
# {
|
|
130
|
+
# layer: 0,
|
|
131
|
+
# capacity: 1024,
|
|
132
|
+
# count: 1024,
|
|
133
|
+
# size_bytes: 1229,
|
|
134
|
+
# num_hashes: 7,
|
|
135
|
+
# bits_set: 5234,
|
|
136
|
+
# total_bits: 9832,
|
|
137
|
+
# fill_ratio: 0.532,
|
|
138
|
+
# error_rate: 0.0015
|
|
139
|
+
# },
|
|
140
|
+
# # ... more layers
|
|
141
|
+
# ]
|
|
99
142
|
# }
|
|
100
143
|
|
|
101
144
|
puts bloom.inspect
|
|
102
|
-
# => #<FastBloomFilter::Filter
|
|
145
|
+
# => #<FastBloomFilter::Filter v2 layers=2 count=1000 size=2.44KB fill=32.72%>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## How Scalable Bloom Filters Work
|
|
149
|
+
|
|
150
|
+
Traditional Bloom Filters require you to specify capacity upfront. **Scalable Bloom Filters** solve this by:
|
|
151
|
+
|
|
152
|
+
1. **Starting Small**: Begin with a small initial capacity (default: 1024 elements)
|
|
153
|
+
2. **Adding Layers**: When a layer fills up, add a new layer with larger capacity
|
|
154
|
+
3. **Tightening Error Rates**: Each new layer has a tighter error rate to maintain overall FPR
|
|
155
|
+
4. **Smart Growth**: Growth factor decreases over time (2x β 1.75x β 1.5x β 1.25x)
|
|
156
|
+
|
|
157
|
+
### Error Rate Distribution
|
|
158
|
+
|
|
159
|
+
Each layer `i` gets error rate: `total_error_rate Γ (1 - r) Γ r^i`
|
|
160
|
+
|
|
161
|
+
Where `r` is the tightening factor (default: 0.85). This ensures the sum of all layer error rates converges to your target error rate.
|
|
162
|
+
|
|
163
|
+
### Example Growth Pattern
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
Layer 0: capacity=1,024 error_rate=0.0015 (initial)
|
|
167
|
+
Layer 1: capacity=2,048 error_rate=0.0013 (2x growth)
|
|
168
|
+
Layer 2: capacity=3,584 error_rate=0.0011 (1.75x growth)
|
|
169
|
+
Layer 3: capacity=5,376 error_rate=0.0009 (1.5x growth)
|
|
170
|
+
Layer 4: capacity=6,720 error_rate=0.0008 (1.25x growth)
|
|
171
|
+
...
|
|
103
172
|
```
|
|
104
173
|
|
|
105
174
|
## Performance
|
|
106
175
|
|
|
107
176
|
Benchmarks on MacBook Pro M1 (100K elements):
|
|
108
177
|
|
|
109
|
-
| Operation | Bloom Filter | Ruby Set | Speedup |
|
|
110
|
-
|
|
111
|
-
| Add |
|
|
112
|
-
| Check |
|
|
113
|
-
| Memory |
|
|
178
|
+
| Operation | Bloom Filter v2 | Ruby Set | Speedup |
|
|
179
|
+
|-----------|-----------------|----------|---------|
|
|
180
|
+
| Add | 48ms | 120ms | 2.5x |
|
|
181
|
+
| Check | 9ms | 15ms | 1.7x |
|
|
182
|
+
| Memory | 145KB | 2000KB | 13.8x |
|
|
114
183
|
|
|
115
184
|
Run benchmarks yourself:
|
|
116
185
|
|
|
@@ -120,11 +189,12 @@ ruby demo.rb
|
|
|
120
189
|
|
|
121
190
|
## Use Cases
|
|
122
191
|
|
|
123
|
-
### Rails: Prevent Duplicate Email Signups
|
|
192
|
+
### Rails: Prevent Duplicate Email Signups (No Capacity Guessing!)
|
|
124
193
|
|
|
125
194
|
```ruby
|
|
126
195
|
class User < ApplicationRecord
|
|
127
|
-
|
|
196
|
+
# No need to guess how many users you'll have!
|
|
197
|
+
SIGNUP_BLOOM = FastBloomFilter.for_emails(error_rate: 0.001)
|
|
128
198
|
|
|
129
199
|
before_validation :check_duplicate_signup
|
|
130
200
|
|
|
@@ -140,12 +210,13 @@ class User < ApplicationRecord
|
|
|
140
210
|
end
|
|
141
211
|
```
|
|
142
212
|
|
|
143
|
-
### Track Visited URLs
|
|
213
|
+
### Track Visited URLs (Scales to Millions)
|
|
144
214
|
|
|
145
215
|
```ruby
|
|
146
216
|
class WebCrawler
|
|
147
217
|
def initialize
|
|
148
|
-
|
|
218
|
+
# Starts small, grows as needed
|
|
219
|
+
@visited = FastBloomFilter.for_urls(error_rate: 0.01)
|
|
149
220
|
end
|
|
150
221
|
|
|
151
222
|
def crawl(url)
|
|
@@ -153,6 +224,11 @@ class WebCrawler
|
|
|
153
224
|
|
|
154
225
|
@visited.add(url)
|
|
155
226
|
# ... crawl logic
|
|
227
|
+
|
|
228
|
+
# Check growth
|
|
229
|
+
if @visited.count % 10_000 == 0
|
|
230
|
+
puts "Crawled #{@visited.count} URLs, #{@visited.num_layers} layers"
|
|
231
|
+
end
|
|
156
232
|
end
|
|
157
233
|
end
|
|
158
234
|
```
|
|
@@ -162,7 +238,7 @@ end
|
|
|
162
238
|
```ruby
|
|
163
239
|
class CacheWarmer
|
|
164
240
|
def initialize
|
|
165
|
-
@warmed = FastBloomFilter::Filter.new(
|
|
241
|
+
@warmed = FastBloomFilter::Filter.new(error_rate: 0.001)
|
|
166
242
|
end
|
|
167
243
|
|
|
168
244
|
def warm(key)
|
|
@@ -174,27 +250,31 @@ class CacheWarmer
|
|
|
174
250
|
end
|
|
175
251
|
```
|
|
176
252
|
|
|
177
|
-
##
|
|
178
|
-
|
|
179
|
-
A Bloom Filter is a space-efficient probabilistic data structure that tests whether an element is a member of a set:
|
|
253
|
+
## Migration from v1.x
|
|
180
254
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
255
|
+
**v1.x (Fixed Capacity):**
|
|
256
|
+
```ruby
|
|
257
|
+
bloom = FastBloomFilter::Filter.new(10_000, 0.01)
|
|
258
|
+
bloom = FastBloomFilter.for_emails(100_000)
|
|
259
|
+
```
|
|
185
260
|
|
|
186
|
-
|
|
261
|
+
**v2.x (Scalable):**
|
|
262
|
+
```ruby
|
|
263
|
+
# Recommended: Let it scale automatically
|
|
264
|
+
bloom = FastBloomFilter::Filter.new(error_rate: 0.01)
|
|
187
265
|
|
|
188
|
-
|
|
189
|
-
|
|
266
|
+
# Or with initial capacity hint
|
|
267
|
+
bloom = FastBloomFilter::Filter.new(error_rate: 0.01, initial_capacity: 1000)
|
|
190
268
|
|
|
191
|
-
|
|
269
|
+
# Helper methods also changed
|
|
270
|
+
bloom = FastBloomFilter.for_emails(error_rate: 0.001, initial_capacity: 10_000)
|
|
271
|
+
```
|
|
192
272
|
|
|
193
273
|
## Development
|
|
194
274
|
|
|
195
275
|
```bash
|
|
196
276
|
# Clone the repository
|
|
197
|
-
git clone https://github.com/
|
|
277
|
+
git clone https://github.com/roman-haidarov/fast_bloom_filter.git
|
|
198
278
|
cd fast_bloom_filter
|
|
199
279
|
|
|
200
280
|
# Install dependencies
|
|
@@ -210,7 +290,7 @@ bundle exec rake test
|
|
|
210
290
|
gem build fast_bloom_filter.gemspec
|
|
211
291
|
|
|
212
292
|
# Install locally
|
|
213
|
-
gem install ./fast_bloom_filter-
|
|
293
|
+
gem install ./fast_bloom_filter-2.0.0.gem
|
|
214
294
|
```
|
|
215
295
|
|
|
216
296
|
### Quick Build Script
|
|
@@ -225,6 +305,15 @@ gem install ./fast_bloom_filter-1.0.0.gem
|
|
|
225
305
|
- C compiler (gcc, clang, etc.)
|
|
226
306
|
- Make
|
|
227
307
|
|
|
308
|
+
## Technical Details
|
|
309
|
+
|
|
310
|
+
- **Hash Function**: MurmurHash3 (32-bit)
|
|
311
|
+
- **Bit Array**: Dynamic allocation per layer
|
|
312
|
+
- **Growth Strategy**: Adaptive (2x β 1.75x β 1.5x β 1.25x)
|
|
313
|
+
- **Tightening Factor**: 0.85 (configurable)
|
|
314
|
+
- **Memory Management**: Ruby GC integration with proper cleanup
|
|
315
|
+
- **Thread Safety**: Safe for concurrent reads (writes need external synchronization)
|
|
316
|
+
|
|
228
317
|
## Contributing
|
|
229
318
|
|
|
230
319
|
1. Fork it
|
|
@@ -239,14 +328,15 @@ The gem is available as open source under the terms of the [MIT License](LICENSE
|
|
|
239
328
|
|
|
240
329
|
## Credits
|
|
241
330
|
|
|
242
|
-
-
|
|
243
|
-
-
|
|
331
|
+
- Scalable Bloom Filters algorithm: Almeida, Baquero, PreguiΓ§a, Hutchison (2007)
|
|
332
|
+
- MurmurHash3 implementation: Austin Appleby
|
|
333
|
+
- Original Bloom Filter: Burton Howard Bloom (1970)
|
|
244
334
|
|
|
245
335
|
## Support
|
|
246
336
|
|
|
247
|
-
- π [Report bugs](https://github.com/
|
|
248
|
-
- π‘ [Request features](https://github.com/
|
|
249
|
-
- π [Documentation](https://github.com/
|
|
337
|
+
- π [Report bugs](https://github.com/roman-haidarov/fast_bloom_filter/issues)
|
|
338
|
+
- π‘ [Request features](https://github.com/roman-haidarov/fast_bloom_filter/issues)
|
|
339
|
+
- π [Documentation](https://github.com/roman-haidarov/fast_bloom_filter)
|
|
250
340
|
|
|
251
341
|
## Changelog
|
|
252
342
|
|