BloomFilter-py 1.3.0__tar.gz → 2.0.0__tar.gz
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.
- bloomfilter_py-2.0.0/CHANGELOG.md +22 -0
- bloomfilter_py-2.0.0/MANIFEST.in +4 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/PKG-INFO +116 -6
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/README.md +112 -1
- bloomfilter_py-2.0.0/benchmarks/benchmark_backends.py +134 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/bloomfilter/__init__.py +2 -0
- bloomfilter_py-2.0.0/bloomfilter/_bitarray.py +300 -0
- bloomfilter_py-2.0.0/bloomfilter/_murmur3.py +63 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/bloomfilter/bloomfilter.py +151 -31
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/bloomfilter/bloomfilter_strategy.py +59 -19
- bloomfilter_py-2.0.0/bloomfilter/py.typed +0 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/bloomfilter_py.egg-info/PKG-INFO +116 -6
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/bloomfilter_py.egg-info/SOURCES.txt +13 -1
- bloomfilter_py-2.0.0/bloomfilter_py.egg-info/requires.txt +4 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/pyproject.toml +19 -8
- bloomfilter_py-2.0.0/scripts/check_api_compatibility.py +259 -0
- bloomfilter_py-2.0.0/scripts/verify_distribution.py +93 -0
- bloomfilter_py-2.0.0/tests/__init__.py +16 -0
- bloomfilter_py-2.0.0/tests/data/murmur3-vectors.json +3642 -0
- bloomfilter_py-2.0.0/tests/java/GuavaBloomFilterInterop.java +301 -0
- bloomfilter_py-2.0.0/tests/test_backends.py +454 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/tests/test_bloomfilter.py +119 -1
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/tests/test_guava_interop.py +21 -9
- bloomfilter_py-1.3.0/bloomfilter_py.egg-info/requires.txt +0 -10
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/LICENSE +0 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/bloomfilter/funnel.py +0 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/bloomfilter_py.egg-info/dependency_links.txt +0 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/bloomfilter_py.egg-info/top_level.txt +0 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/setup.cfg +0 -0
- {bloomfilter_py-1.3.0 → bloomfilter_py-2.0.0}/tests/test_filter_operations.py +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
- Retain master security checks that bound hexadecimal and Base64 decoding
|
|
6
|
+
before allocating the complete serialized payload.
|
|
7
|
+
- Remove all mandatory runtime dependencies; ship packed bit storage and a
|
|
8
|
+
seed-zero MurmurHash3 x64_128 implementation supporting Python 3.8+.
|
|
9
|
+
- Add explicit `backend="python"` (default) and `backend="native"` modes.
|
|
10
|
+
Native acceleration is available through the `native` installation extra.
|
|
11
|
+
- Preserve both hashing strategies, existing funnels and the Guava compact
|
|
12
|
+
serialization format. Support cross-backend comparison and merging.
|
|
13
|
+
- Replace per-word serialization objects with bounded chunk transformations;
|
|
14
|
+
chunk large counts and merges as well.
|
|
15
|
+
- Export a stable `BitArray` facade. `setup` copies external third-party
|
|
16
|
+
bitarray inputs; third-party-only data methods and aliasing need migration.
|
|
17
|
+
- Reject non-finite error rates, nonintegral sizing and misbehaving streams.
|
|
18
|
+
- Add fixed and random hash differential tests, packed-bit/backend tests,
|
|
19
|
+
memory checks, packaging validation, benchmarks and a dual-backend CI matrix.
|
|
20
|
+
|
|
21
|
+
Public bit storage and external-array aliasing changes require a major version.
|
|
22
|
+
The version is prepared locally; publishing requires a separate release action.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bloomfilter-py
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Yet another bloomfilter implementation in Python
|
|
5
5
|
Author-email: OldPanda <me@old-panda.com>
|
|
6
6
|
License: MIT License
|
|
@@ -47,10 +47,9 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
47
47
|
Requires-Python: >=3.8
|
|
48
48
|
Description-Content-Type: text/markdown
|
|
49
49
|
License-File: LICENSE
|
|
50
|
-
|
|
51
|
-
Requires-Dist:
|
|
52
|
-
Requires-Dist: mmh3
|
|
53
|
-
Requires-Dist: mmh3==5.3.1; python_version >= "3.10"
|
|
50
|
+
Provides-Extra: native
|
|
51
|
+
Requires-Dist: bitarray<4,>=3.0; extra == "native"
|
|
52
|
+
Requires-Dist: mmh3<6,>=5.0; extra == "native"
|
|
54
53
|
Dynamic: license-file
|
|
55
54
|
|
|
56
55
|
# bloomfilter-py
|
|
@@ -81,6 +80,68 @@ Here's a brief [introduction](https://en.wikipedia.org/wiki/Bloom_filter) to Blo
|
|
|
81
80
|
pip install bloomfilter-py
|
|
82
81
|
```
|
|
83
82
|
|
|
83
|
+
## Backends and migration from 1.3
|
|
84
|
+
|
|
85
|
+
Version 2.0 runs on the Python standard library alone. `bitarray` and `mmh3`
|
|
86
|
+
are no longer required to install or import this package. The default is always
|
|
87
|
+
`backend="python"`, even if those packages are already installed. For explicit
|
|
88
|
+
native acceleration:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install 'bloomfilter-py[native]'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from bloomfilter import BloomFilter
|
|
96
|
+
|
|
97
|
+
fast = BloomFilter(500, 0.01, backend="native")
|
|
98
|
+
fast.put("example")
|
|
99
|
+
loaded = BloomFilter.loads(fast.dumps(), backend="native")
|
|
100
|
+
assert "example" in loaded
|
|
101
|
+
assert loaded.backend == "native"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The constructor and all loading methods accept the keyword-only `backend`
|
|
105
|
+
argument (`"python"` or `"native"`). An invalid name raises `ValueError`;
|
|
106
|
+
requesting native mode without **both** optional packages raises `ImportError`
|
|
107
|
+
with installation instructions. There is no automatic fallback. `copy()`
|
|
108
|
+
preserves the backend. Filters using different backends can be compared and
|
|
109
|
+
merged when their strategies, funnels, sizes, and hash counts are compatible.
|
|
110
|
+
Backend choice is not serialized: all old dumps remain readable, and new dumps
|
|
111
|
+
use exactly the same Guava compact format.
|
|
112
|
+
|
|
113
|
+
Pure Python hashing is slower than the C extension, especially on long keys.
|
|
114
|
+
Native mode is available for throughput-sensitive workloads. Serialization,
|
|
115
|
+
counting, and merging process at most 64 KiB of payload per chunk; copies and
|
|
116
|
+
returned dumps necessarily allocate their complete result. `dumps()` and
|
|
117
|
+
`loads()` also retain full input/output bytes, so use stream methods for large
|
|
118
|
+
filters. Backing storage remains packed (roughly one byte per eight bits).
|
|
119
|
+
|
|
120
|
+
### Bit storage and custom strategies
|
|
121
|
+
|
|
122
|
+
`filter.data` is now a `bloomfilter.BitArray` facade with consistent semantics
|
|
123
|
+
on either backend. It supports `len`, iteration, negative/integer indexing,
|
|
124
|
+
slice copies, scalar or equal-length iterable slice assignment, `setall`,
|
|
125
|
+
`count(0/1)`, `copy`, equality, `|=`, `+=`, `frombytes`, and `tobytes`.
|
|
126
|
+
Slices preserve the backend; slice assignment cannot resize. Appending via
|
|
127
|
+
`frombytes` or `+=` works even at non-byte-aligned lengths. `tobytes()` uses
|
|
128
|
+
most-significant-bit-first bytes with zero padding at the end. The buffer
|
|
129
|
+
protocol and the complete third-party `bitarray` API are **not** provided.
|
|
130
|
+
|
|
131
|
+
Existing `setup(hash_count, data, strategy, funnel)` calls accept this facade.
|
|
132
|
+
For migration, a third-party `bitarray.bitarray` is also accepted when installed;
|
|
133
|
+
it is copied into Python-backed storage and normalized to logical bit order,
|
|
134
|
+
including little-endian inputs. It is no longer aliased to `filter.data`.
|
|
135
|
+
Callers relying on that alias or on third-party-only methods must update.
|
|
136
|
+
Passing a `BitArray` to `setup` retains it, as before, so external mutations
|
|
137
|
+
remain visible. `backend` reflects the current data object.
|
|
138
|
+
|
|
139
|
+
Custom strategies still receive the same positional `put` / `might_contain`
|
|
140
|
+
arguments, now with the facade as their data argument. Overrides of built-in
|
|
141
|
+
strategies' `hash_key` are honored on both backends. Strategies must not depend
|
|
142
|
+
on private storage fields or a specific third-party data type. Direct calls to built-in strategies with legacy third-party bit arrays remain
|
|
143
|
+
supported. Direct calls to `Strategy.hash_key` always use the standard-library MurmurHash3 implementation.
|
|
144
|
+
|
|
84
145
|
## Usage Examples
|
|
85
146
|
|
|
86
147
|
### Basic Usage
|
|
@@ -291,10 +352,59 @@ install the additional tools and execute:
|
|
|
291
352
|
|
|
292
353
|
```bash
|
|
293
354
|
python -m pip install black mypy bandit
|
|
294
|
-
python -m black --check bloomfilter tests
|
|
355
|
+
python -m black --check bloomfilter tests benchmarks scripts
|
|
295
356
|
python -m mypy bloomfilter --strict
|
|
296
357
|
python -m bandit -r bloomfilter
|
|
297
358
|
```
|
|
298
359
|
|
|
299
360
|
On Windows PowerShell, activate the virtual environment with
|
|
300
361
|
`.venv\Scripts\Activate.ps1` instead of `source .venv/bin/activate`.
|
|
362
|
+
|
|
363
|
+
### Reproducible backend benchmarks
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
python benchmarks/benchmark_backends.py --backend python --output python.json
|
|
367
|
+
python benchmarks/benchmark_backends.py --backend native --output native.json
|
|
368
|
+
# Against a separate checkout of 1.3.0, with its dependencies installed:
|
|
369
|
+
python benchmarks/benchmark_backends.py --backend baseline \
|
|
370
|
+
--package-path /path/to/1.3.0 --output baseline.json
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The script reports median timings, raw repeats, workload sizes, and interpreter
|
|
374
|
+
metadata. It covers fresh and repeated insertions, hits and misses, short and
|
|
375
|
+
long byte keys, serialization, statistics, and merging. Results describe the
|
|
376
|
+
specified workload rather than a universal throughput guarantee.
|
|
377
|
+
|
|
378
|
+
For the zero-dependency source suite run `python -S -m unittest discover`.
|
|
379
|
+
Install `.[native]` to enable optional differential tests against the C packages.
|
|
380
|
+
The checked-in hash vectors were generated with mmh3 5.3.1 and cover every
|
|
381
|
+
length from 0 through 512 plus larger block boundaries.
|
|
382
|
+
Run Java integration tests for both backends with `BLOOMFILTER_TEST_BACKEND`
|
|
383
|
+
set to `python` and `native` respectively. CI covers Python 3.8 through 3.14.
|
|
384
|
+
|
|
385
|
+
Build and verify release artifacts with `python -m build` followed by
|
|
386
|
+
`python scripts/verify_distribution.py dist`. The latter installs the wheel
|
|
387
|
+
offline into a clean temporary environment and verifies it outside the source
|
|
388
|
+
path, including the absence of mandatory runtime dependencies.
|
|
389
|
+
|
|
390
|
+
### Auditing compatibility against an earlier checkout
|
|
391
|
+
|
|
392
|
+
Use an environment containing the baseline's dependencies to compare actual
|
|
393
|
+
behavior in separate processes:
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
python scripts/check_api_compatibility.py --baseline /path/to/baseline \
|
|
397
|
+
--candidate . --output compatibility.json
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
The audit checks twelve strategy/funnel combinations, serialized bytes, query
|
|
401
|
+
results, copies/merges/statistics, streams, custom hashes and existing call
|
|
402
|
+
signatures. It separately reports changes to bit-storage type, third-party
|
|
403
|
+
methods, buffer support and `setup` input aliasing. A successful audit command
|
|
404
|
+
means the comparison completed; inspect `fully_backward_compatible` for the
|
|
405
|
+
compatibility verdict. Version 2.0 preserves the tested core operations but
|
|
406
|
+
changes these public storage behaviors, so it is a major release.
|
|
407
|
+
|
|
408
|
+
Hex and Base64 loaders include the master allocation-bound security update:
|
|
409
|
+
encoded sizes and headers are checked before full Base64 decoding, and hex
|
|
410
|
+
inputs are decoded in bounded chunks even with substantial whitespace.
|
|
@@ -26,6 +26,68 @@ Here's a brief [introduction](https://en.wikipedia.org/wiki/Bloom_filter) to Blo
|
|
|
26
26
|
pip install bloomfilter-py
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## Backends and migration from 1.3
|
|
30
|
+
|
|
31
|
+
Version 2.0 runs on the Python standard library alone. `bitarray` and `mmh3`
|
|
32
|
+
are no longer required to install or import this package. The default is always
|
|
33
|
+
`backend="python"`, even if those packages are already installed. For explicit
|
|
34
|
+
native acceleration:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install 'bloomfilter-py[native]'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from bloomfilter import BloomFilter
|
|
42
|
+
|
|
43
|
+
fast = BloomFilter(500, 0.01, backend="native")
|
|
44
|
+
fast.put("example")
|
|
45
|
+
loaded = BloomFilter.loads(fast.dumps(), backend="native")
|
|
46
|
+
assert "example" in loaded
|
|
47
|
+
assert loaded.backend == "native"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The constructor and all loading methods accept the keyword-only `backend`
|
|
51
|
+
argument (`"python"` or `"native"`). An invalid name raises `ValueError`;
|
|
52
|
+
requesting native mode without **both** optional packages raises `ImportError`
|
|
53
|
+
with installation instructions. There is no automatic fallback. `copy()`
|
|
54
|
+
preserves the backend. Filters using different backends can be compared and
|
|
55
|
+
merged when their strategies, funnels, sizes, and hash counts are compatible.
|
|
56
|
+
Backend choice is not serialized: all old dumps remain readable, and new dumps
|
|
57
|
+
use exactly the same Guava compact format.
|
|
58
|
+
|
|
59
|
+
Pure Python hashing is slower than the C extension, especially on long keys.
|
|
60
|
+
Native mode is available for throughput-sensitive workloads. Serialization,
|
|
61
|
+
counting, and merging process at most 64 KiB of payload per chunk; copies and
|
|
62
|
+
returned dumps necessarily allocate their complete result. `dumps()` and
|
|
63
|
+
`loads()` also retain full input/output bytes, so use stream methods for large
|
|
64
|
+
filters. Backing storage remains packed (roughly one byte per eight bits).
|
|
65
|
+
|
|
66
|
+
### Bit storage and custom strategies
|
|
67
|
+
|
|
68
|
+
`filter.data` is now a `bloomfilter.BitArray` facade with consistent semantics
|
|
69
|
+
on either backend. It supports `len`, iteration, negative/integer indexing,
|
|
70
|
+
slice copies, scalar or equal-length iterable slice assignment, `setall`,
|
|
71
|
+
`count(0/1)`, `copy`, equality, `|=`, `+=`, `frombytes`, and `tobytes`.
|
|
72
|
+
Slices preserve the backend; slice assignment cannot resize. Appending via
|
|
73
|
+
`frombytes` or `+=` works even at non-byte-aligned lengths. `tobytes()` uses
|
|
74
|
+
most-significant-bit-first bytes with zero padding at the end. The buffer
|
|
75
|
+
protocol and the complete third-party `bitarray` API are **not** provided.
|
|
76
|
+
|
|
77
|
+
Existing `setup(hash_count, data, strategy, funnel)` calls accept this facade.
|
|
78
|
+
For migration, a third-party `bitarray.bitarray` is also accepted when installed;
|
|
79
|
+
it is copied into Python-backed storage and normalized to logical bit order,
|
|
80
|
+
including little-endian inputs. It is no longer aliased to `filter.data`.
|
|
81
|
+
Callers relying on that alias or on third-party-only methods must update.
|
|
82
|
+
Passing a `BitArray` to `setup` retains it, as before, so external mutations
|
|
83
|
+
remain visible. `backend` reflects the current data object.
|
|
84
|
+
|
|
85
|
+
Custom strategies still receive the same positional `put` / `might_contain`
|
|
86
|
+
arguments, now with the facade as their data argument. Overrides of built-in
|
|
87
|
+
strategies' `hash_key` are honored on both backends. Strategies must not depend
|
|
88
|
+
on private storage fields or a specific third-party data type. Direct calls to built-in strategies with legacy third-party bit arrays remain
|
|
89
|
+
supported. Direct calls to `Strategy.hash_key` always use the standard-library MurmurHash3 implementation.
|
|
90
|
+
|
|
29
91
|
## Usage Examples
|
|
30
92
|
|
|
31
93
|
### Basic Usage
|
|
@@ -236,10 +298,59 @@ install the additional tools and execute:
|
|
|
236
298
|
|
|
237
299
|
```bash
|
|
238
300
|
python -m pip install black mypy bandit
|
|
239
|
-
python -m black --check bloomfilter tests
|
|
301
|
+
python -m black --check bloomfilter tests benchmarks scripts
|
|
240
302
|
python -m mypy bloomfilter --strict
|
|
241
303
|
python -m bandit -r bloomfilter
|
|
242
304
|
```
|
|
243
305
|
|
|
244
306
|
On Windows PowerShell, activate the virtual environment with
|
|
245
307
|
`.venv\Scripts\Activate.ps1` instead of `source .venv/bin/activate`.
|
|
308
|
+
|
|
309
|
+
### Reproducible backend benchmarks
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
python benchmarks/benchmark_backends.py --backend python --output python.json
|
|
313
|
+
python benchmarks/benchmark_backends.py --backend native --output native.json
|
|
314
|
+
# Against a separate checkout of 1.3.0, with its dependencies installed:
|
|
315
|
+
python benchmarks/benchmark_backends.py --backend baseline \
|
|
316
|
+
--package-path /path/to/1.3.0 --output baseline.json
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The script reports median timings, raw repeats, workload sizes, and interpreter
|
|
320
|
+
metadata. It covers fresh and repeated insertions, hits and misses, short and
|
|
321
|
+
long byte keys, serialization, statistics, and merging. Results describe the
|
|
322
|
+
specified workload rather than a universal throughput guarantee.
|
|
323
|
+
|
|
324
|
+
For the zero-dependency source suite run `python -S -m unittest discover`.
|
|
325
|
+
Install `.[native]` to enable optional differential tests against the C packages.
|
|
326
|
+
The checked-in hash vectors were generated with mmh3 5.3.1 and cover every
|
|
327
|
+
length from 0 through 512 plus larger block boundaries.
|
|
328
|
+
Run Java integration tests for both backends with `BLOOMFILTER_TEST_BACKEND`
|
|
329
|
+
set to `python` and `native` respectively. CI covers Python 3.8 through 3.14.
|
|
330
|
+
|
|
331
|
+
Build and verify release artifacts with `python -m build` followed by
|
|
332
|
+
`python scripts/verify_distribution.py dist`. The latter installs the wheel
|
|
333
|
+
offline into a clean temporary environment and verifies it outside the source
|
|
334
|
+
path, including the absence of mandatory runtime dependencies.
|
|
335
|
+
|
|
336
|
+
### Auditing compatibility against an earlier checkout
|
|
337
|
+
|
|
338
|
+
Use an environment containing the baseline's dependencies to compare actual
|
|
339
|
+
behavior in separate processes:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
python scripts/check_api_compatibility.py --baseline /path/to/baseline \
|
|
343
|
+
--candidate . --output compatibility.json
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The audit checks twelve strategy/funnel combinations, serialized bytes, query
|
|
347
|
+
results, copies/merges/statistics, streams, custom hashes and existing call
|
|
348
|
+
signatures. It separately reports changes to bit-storage type, third-party
|
|
349
|
+
methods, buffer support and `setup` input aliasing. A successful audit command
|
|
350
|
+
means the comparison completed; inspect `fully_backward_compatible` for the
|
|
351
|
+
compatibility verdict. Version 2.0 preserves the tested core operations but
|
|
352
|
+
changes these public storage behaviors, so it is a major release.
|
|
353
|
+
|
|
354
|
+
Hex and Base64 loaders include the master allocation-bound security update:
|
|
355
|
+
encoded sizes and headers are checked before full Base64 decoding, and hex
|
|
356
|
+
inputs are decoded in bounded chunks even with substantial whitespace.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Reproducible stdlib-only driver; baseline/native modes need their packages."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import platform
|
|
8
|
+
import statistics
|
|
9
|
+
import sys
|
|
10
|
+
import timeit
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"--backend", choices=("python", "native", "baseline"), default="python"
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--package-path", type=Path, default=Path(__file__).resolve().parents[1]
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
22
|
+
parser.add_argument("--repeat", type=int, default=5)
|
|
23
|
+
parser.add_argument("--number", type=int, default=3)
|
|
24
|
+
args = parser.parse_args()
|
|
25
|
+
if args.repeat < 1 or args.number < 1:
|
|
26
|
+
parser.error("repeat and number must be positive")
|
|
27
|
+
sys.path.insert(0, str(args.package_path.resolve()))
|
|
28
|
+
from bloomfilter import BloomFilter, BYTE_ARRAY_FUNNEL
|
|
29
|
+
|
|
30
|
+
def make(expected=5000):
|
|
31
|
+
if args.backend == "baseline":
|
|
32
|
+
return BloomFilter(expected, 0.01, funnel=BYTE_ARRAY_FUNNEL)
|
|
33
|
+
return BloomFilter(
|
|
34
|
+
expected, 0.01, funnel=BYTE_ARRAY_FUNNEL, backend=args.backend
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
measurements = {}
|
|
38
|
+
fingerprints = {}
|
|
39
|
+
for key_size in (16, 1024):
|
|
40
|
+
keys = [
|
|
41
|
+
i.to_bytes(4, "little") + bytes([i % 256]) * (key_size - 4)
|
|
42
|
+
for i in range(500)
|
|
43
|
+
]
|
|
44
|
+
misses = [
|
|
45
|
+
(i + 500).to_bytes(4, "little") + bytes([(i + 500) % 256]) * (key_size - 4)
|
|
46
|
+
for i in range(500)
|
|
47
|
+
]
|
|
48
|
+
bf = make()
|
|
49
|
+
for key in keys:
|
|
50
|
+
bf.put(key)
|
|
51
|
+
fingerprints[str(key_size)] = hashlib.sha256(bf.dumps()).hexdigest()
|
|
52
|
+
|
|
53
|
+
def insert_new():
|
|
54
|
+
fresh = make()
|
|
55
|
+
for key in keys:
|
|
56
|
+
fresh.put(key)
|
|
57
|
+
|
|
58
|
+
tasks = {
|
|
59
|
+
"insert_new": insert_new,
|
|
60
|
+
"insert_repeat": lambda: [bf.put(key) for key in keys],
|
|
61
|
+
"query_hit": lambda: [bf.might_contain(key) for key in keys],
|
|
62
|
+
"query_miss": lambda: [bf.might_contain(key) for key in misses],
|
|
63
|
+
}
|
|
64
|
+
for name, task in tasks.items():
|
|
65
|
+
raw = [
|
|
66
|
+
value / args.number
|
|
67
|
+
for value in timeit.repeat(task, number=args.number, repeat=args.repeat)
|
|
68
|
+
]
|
|
69
|
+
measurements[f"{name}_{key_size}B_500keys"] = {
|
|
70
|
+
"median_seconds": statistics.median(raw),
|
|
71
|
+
"raw_seconds": raw,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Approximately 1 MiB, enough to exercise bounded chunks repeatedly.
|
|
75
|
+
large = make(875000)
|
|
76
|
+
for i in range(500):
|
|
77
|
+
large.put(i.to_bytes(4, "little"))
|
|
78
|
+
serialized = large.dumps()
|
|
79
|
+
second = large.copy()
|
|
80
|
+
second.put(b"second-filter")
|
|
81
|
+
if args.backend == "baseline":
|
|
82
|
+
load = lambda: BloomFilter.loads(serialized, BYTE_ARRAY_FUNNEL)
|
|
83
|
+
else:
|
|
84
|
+
load = lambda: BloomFilter.loads(
|
|
85
|
+
serialized, BYTE_ARRAY_FUNNEL, backend=args.backend
|
|
86
|
+
)
|
|
87
|
+
for name, task in {
|
|
88
|
+
"dump_1MiB": large.dumps,
|
|
89
|
+
"load_1MiB": load,
|
|
90
|
+
"statistics_1MiB": large.expected_fpp,
|
|
91
|
+
"merge_repeat_1MiB": lambda: large.put_all(second),
|
|
92
|
+
}.items():
|
|
93
|
+
raw = [
|
|
94
|
+
value / args.number
|
|
95
|
+
for value in timeit.repeat(task, number=args.number, repeat=args.repeat)
|
|
96
|
+
]
|
|
97
|
+
measurements[name] = {
|
|
98
|
+
"median_seconds": statistics.median(raw),
|
|
99
|
+
"raw_seconds": raw,
|
|
100
|
+
}
|
|
101
|
+
report = {
|
|
102
|
+
"backend": args.backend,
|
|
103
|
+
"python": sys.version,
|
|
104
|
+
"platform": platform.platform(),
|
|
105
|
+
"package_path": str(args.package_path.resolve()),
|
|
106
|
+
"repeat": args.repeat,
|
|
107
|
+
"number": args.number,
|
|
108
|
+
"short_filter_capacity": 5000,
|
|
109
|
+
"fpp": 0.01,
|
|
110
|
+
"keys_per_batch": 500,
|
|
111
|
+
"large_filter_bytes": len(serialized),
|
|
112
|
+
"fingerprints": fingerprints,
|
|
113
|
+
"notes": [
|
|
114
|
+
"Fresh insertion includes constructing an empty filter.",
|
|
115
|
+
"Repeated insertion and merge operate on already populated filters.",
|
|
116
|
+
"Query misses are absent keys and may include false positives.",
|
|
117
|
+
"Operations return full serialized bytes for dump/load.",
|
|
118
|
+
],
|
|
119
|
+
"measurements": measurements,
|
|
120
|
+
}
|
|
121
|
+
args.output.write_text(json.dumps(report, indent=2) + "\n")
|
|
122
|
+
print(
|
|
123
|
+
json.dumps(
|
|
124
|
+
{
|
|
125
|
+
name: round(value["median_seconds"] * 1000, 3)
|
|
126
|
+
for name, value in measurements.items()
|
|
127
|
+
},
|
|
128
|
+
indent=2,
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from bloomfilter._bitarray import BitArray
|
|
1
2
|
from bloomfilter.bloomfilter import BloomFilter
|
|
2
3
|
from bloomfilter.funnel import (
|
|
3
4
|
BYTE_ARRAY_FUNNEL,
|
|
@@ -11,6 +12,7 @@ from bloomfilter.funnel import (
|
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
13
14
|
"BloomFilter",
|
|
15
|
+
"BitArray",
|
|
14
16
|
"Funnel",
|
|
15
17
|
"StringFunnel",
|
|
16
18
|
"INTEGER_FUNNEL",
|