Skip to content

Commit 2ee9fc4

Browse files
committed
feat(segmenter): use k=2 blocked bloom filter to improve performance
1 parent 5bb8e4e commit 2ee9fc4

2 files changed

Lines changed: 50 additions & 13 deletions

File tree

doc/mkdwarfs.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,24 @@ Most other options are concerned with compression tuning:
176176
and `mkdwarfs` will be slightly slower and use more memory.
177177

178178
- `--bloom-filter-size`=[*category*`::`]*value*:
179-
The segmenting algorithm uses a bloom filter to determine quickly if
180-
there is *no* match at a given position. This will filter out more than
181-
90% of bad matches quickly with the default bloom filter size. The default
182-
is pretty much where the sweet spot lies. If you have copious amounts of
183-
RAM and CPU power, feel free to increase this by one or two and you *might*
184-
be able to see some improvement. If your system is tight on memory, then
185-
decreasing this will potentially save a few MiBs.
179+
The segmenting algorithm uses a bloom filter to determine quickly if there
180+
is *no* match at a given position. This will usually filter out more than
181+
97% of bad matches quickly with the default bloom filter size. The default
182+
of 4 is very close to the sweet spot for the default settings. Note that
183+
the actual bloom filter memory size is a function of the block size (`-S`),
184+
the window step size (`-w`), and the lookback size (`-B`). This option is
185+
merely another 2^n multiplier on top of that. With every increase of this
186+
option value by 1, the bloom filter memory size doubles, but the number of
187+
false positives is reduced by a factor of around 3.5. However, increasing the
188+
value also means the bloom filter is less likely to fit into the CPU cache.
189+
It's a trade-off and it's hardware-dependent. The impact of the bloom filter
190+
is more relevant as you increase the lookback size, as in addition to a
191+
"global" bloom filter, each lookback block has its own "local" bloom filter,
192+
so more filters need to be checked if a candidate isn't rejected by the
193+
global filter. This means that as you increase the lookback size, it may
194+
pay off to nudge this value up to 5 (if you have enough RAM to spare).
195+
It is usually not recommended to drop this below 4 unless you're under
196+
pressure to save memory, as it will noticeably hurt segmenter speed.
186197

187198
- `-L`, `--memory-limit=auto|`*value*:
188199
Approximately how much memory you want `mkdwarfs` to use during filesystem

src/writer/segmenter.cpp

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,20 +221,19 @@ class alignas(64) bloom_filter {
221221
}
222222
}
223223

224-
DWARFS_FORCE_INLINE void add(size_t ix) {
224+
DWARFS_FORCE_INLINE void add(size_t const ix) {
225225
assert(bits_);
226226
auto bits = bits_;
227227
BOOST_ALIGN_ASSUME_ALIGNED(bits, sizeof(bits_type));
228-
bits[(ix >> index_shift) & index_mask_] |= static_cast<bits_type>(1)
229-
<< (ix & value_mask);
228+
bits[(ix >> index_shift) & index_mask_] |= mask(ix);
230229
}
231230

232-
DWARFS_FORCE_INLINE bool test(size_t ix) const {
231+
DWARFS_FORCE_INLINE bool test(size_t const ix) const {
233232
assert(bits_);
234233
auto bits = bits_;
235234
BOOST_ALIGN_ASSUME_ALIGNED(bits, sizeof(bits_type));
236-
return bits[(ix >> index_shift) & index_mask_] &
237-
(static_cast<bits_type>(1) << (ix & value_mask));
235+
auto const m = mask(ix);
236+
return (bits[(ix >> index_shift) & index_mask_] & m) == m;
238237
}
239238

240239
// size in bits
@@ -258,6 +257,33 @@ class alignas(64) bloom_filter {
258257
uint64_t memory_usage() const { return size_ / 8; }
259258

260259
private:
260+
DWARFS_FORCE_INLINE static constexpr bits_type mask(size_t const ix) {
261+
if constexpr (sizeof(bits_type) >= 8) {
262+
//
263+
// Make this a blocked bloom filter with k=2. This significantly
264+
// increases the rejection rate and stat-sig improves performance
265+
// of the segmenter:
266+
//
267+
// ------------------------------------------------------------
268+
// block size | lookback | rejection rate | true positive rate
269+
// | | k=1 | k=2 | k=1 | k=2
270+
// ------------------------------------------------------------
271+
// 64 MiB | 1 | 96.6% | 99.1% | 0.48% | 1.89%
272+
// 16 MiB | 1 | 96.4% | 99.0% | 0.35% | 1.26%
273+
// 64 MiB | 8 | 94.5% | 98.5% | 1.05% | 3.88%
274+
// ------------------------------------------------------------
275+
//
276+
// 64 MiB, lookback=1:
277+
// k=1 -> 15.976s ± 0.021s
278+
// k=2 -> 15.277s ± 0.017s
279+
//
280+
return (static_cast<bits_type>(1) << (ix & value_mask)) |
281+
(static_cast<bits_type>(1) << ((ix >> 26) & value_mask));
282+
} else {
283+
return static_cast<bits_type>(1) << (ix & value_mask);
284+
}
285+
}
286+
261287
DWARFS_FORCE_INLINE bits_type const* cbegin() const { return bits_; }
262288
DWARFS_FORCE_INLINE bits_type const* cend() const {
263289
return bits_ + (size_ >> index_shift);

0 commit comments

Comments
 (0)