From a1a546a63442578961bad69edfd8de125780e383 Mon Sep 17 00:00:00 2001 From: Andrew Kryczka Date: Mon, 30 Jul 2018 17:51:31 -0700 Subject: [PATCH] Avoid integer division in filter probing (#4071) Summary: The cache line size was computed dynamically based on the length of the filter bits, and the number of cache-lines encoded in the footer. This calculation had to be dynamic in case users migrate their data between platforms with different cache line sizes. The downside, though, was bloom filter probing became expensive as it did integer mod and division. However, since we know all possible cache line sizes are powers of two, we should be able to use bit shift to find the cache line, and bitwise-and to find the bit within the cache line. To do this, we compute the log-base-two of cache line size in the constructor, and use that in bitwise operations to replace division/mod. Pull Request resolved: https://github.com/facebook/rocksdb/pull/4071 Differential Revision: D8684067 Pulled By: ajkr fbshipit-source-id: 50298872fba5acd01e8269cd7abcc51a095e0f61 --- util/bloom.cc | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/util/bloom.cc b/util/bloom.cc index 51de07953..9757d4b61 100644 --- a/util/bloom.cc +++ b/util/bloom.cc @@ -144,13 +144,31 @@ class FullFilterBitsReader : public FilterBitsReader { : data_(const_cast(contents.data())), data_len_(static_cast(contents.size())), num_probes_(0), - num_lines_(0) { + num_lines_(0), + log2_cache_line_size_(0) { assert(data_); GetFilterMeta(contents, &num_probes_, &num_lines_); // Sanitize broken parameter if (num_lines_ != 0 && (data_len_-5) % num_lines_ != 0) { num_lines_ = 0; num_probes_ = 0; + } else if (num_lines_ != 0) { + while (true) { + uint32_t num_lines_at_curr_cache_size = + (data_len_ - 5) >> log2_cache_line_size_; + if (num_lines_at_curr_cache_size == 0) { + // The cache line size seems not a power of two. It's not supported + // and indicates a corruption so disable using this filter. + assert(false); + num_lines_ = 0; + num_probes_ = 0; + break; + } + if (num_lines_at_curr_cache_size == num_lines_) { + break; + } + ++log2_cache_line_size_; + } } } @@ -173,6 +191,7 @@ class FullFilterBitsReader : public FilterBitsReader { uint32_t data_len_; size_t num_probes_; uint32_t num_lines_; + uint32_t log2_cache_line_size_; // Get num_probes, and num_lines from filter // If filter format broken, set both to 0. @@ -222,19 +241,20 @@ bool FullFilterBitsReader::HashMayMatch(const uint32_t& hash, // It is ensured the params are valid before calling it assert(num_probes != 0); assert(num_lines != 0 && (len - 5) % num_lines == 0); - uint32_t cache_line_size = (len - 5) / num_lines; const char* data = filter.data(); uint32_t h = hash; const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits - uint32_t b = (h % num_lines) * (cache_line_size * 8); + // Left shift by an extra 3 to convert bytes to bits + uint32_t b = (h % num_lines) << (log2_cache_line_size_ + 3); PREFETCH(&data[b / 8], 0 /* rw */, 1 /* locality */); - PREFETCH(&data[b / 8 + cache_line_size - 1], 0 /* rw */, 1 /* locality */); + PREFETCH(&data[b / 8 + (1 << log2_cache_line_size_) - 1], 0 /* rw */, + 1 /* locality */); for (uint32_t i = 0; i < num_probes; ++i) { // Since CACHE_LINE_SIZE is defined as 2^n, this line will be optimized // to a simple and operation by compiler. - const uint32_t bitpos = b + (h % (cache_line_size * 8)); + const uint32_t bitpos = b + (h & ((1 << (log2_cache_line_size_ + 3)) - 1)); if (((data[bitpos / 8]) & (1 << (bitpos % 8))) == 0) { return false; }