// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. // This source code is licensed under both the GPLv2 (found in the // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). #if !defined(GFLAGS) #include int main() { fprintf(stderr, "filter_bench requires gflags\n"); return 1; } #else #include #include #include #include #include "memory/arena.h" #include "port/port.h" #include "port/stack_trace.h" #include "rocksdb/cache.h" #include "rocksdb/env.h" #include "rocksdb/system_clock.h" #include "rocksdb/table.h" #include "table/block_based/filter_policy_internal.h" #include "table/block_based/full_filter_block.h" #include "table/block_based/mock_block_based_table.h" #include "table/plain/plain_table_bloom.h" #include "util/cast_util.h" #include "util/gflags_compat.h" #include "util/hash.h" #include "util/random.h" #include "util/stderr_logger.h" #include "util/stop_watch.h" #include "util/string_util.h" using GFLAGS_NAMESPACE::ParseCommandLineFlags; using GFLAGS_NAMESPACE::RegisterFlagValidator; using GFLAGS_NAMESPACE::SetUsageMessage; DEFINE_uint32(seed, 0, "Seed for random number generators"); DEFINE_double(working_mem_size_mb, 200, "MB of memory to get up to among all filters, unless " "m_keys_total_max is specified."); DEFINE_uint32(average_keys_per_filter, 10000, "Average number of keys per filter"); DEFINE_double(vary_key_count_ratio, 0.4, "Vary number of keys by up to +/- vary_key_count_ratio * " "average_keys_per_filter."); DEFINE_uint32(key_size, 24, "Average number of bytes for each key"); DEFINE_bool(vary_key_alignment, true, "Whether to vary key alignment (default: at least 32-bit " "alignment)"); DEFINE_uint32(vary_key_size_log2_interval, 5, "Use same key size 2^n times, then change. Key size varies from " "-2 to +2 bytes vs. average, unless n>=30 to fix key size."); DEFINE_uint32(batch_size, 8, "Number of keys to group in each batch"); DEFINE_double(bits_per_key, 10.0, "Bits per key setting for filters"); DEFINE_double(m_queries, 200, "Millions of queries for each test mode"); DEFINE_double(m_keys_total_max, 0, "Maximum total keys added to filters, in millions. " "0 (default) disables. Non-zero overrides working_mem_size_mb " "option."); DEFINE_bool(use_full_block_reader, false, "Use FullFilterBlockReader interface rather than FilterBitsReader"); DEFINE_bool(use_plain_table_bloom, false, "Use PlainTableBloom structure and interface rather than " "FilterBitsReader/FullFilterBlockReader"); DEFINE_bool(new_builder, false, "Whether to create a new builder for each new filter"); DEFINE_uint32(impl, 0, "Select filter implementation. Without -use_plain_table_bloom:" "0 = legacy full Bloom filter, " "1 = format_version 5 Bloom filter, 2 = Ribbon128 filter. With " "-use_plain_table_bloom: 0 = no locality, 1 = locality."); DEFINE_bool(net_includes_hashing, false, "Whether query net ns/op times should include hashing. " "(if not, dry run will include hashing) " "(build times always include hashing)"); DEFINE_bool(optimize_filters_for_memory, false, "Setting for BlockBasedTableOptions::optimize_filters_for_memory"); DEFINE_bool(detect_filter_construct_corruption, false, "Setting for " "BlockBasedTableOptions::detect_filter_construct_corruption"); DEFINE_uint32(block_cache_capacity_MB, 8, "Setting for " "LRUCacheOptions::capacity"); DEFINE_bool(charge_filter_construction, false, "Setting for " "CacheEntryRoleOptions::charged of" "CacheEntryRole::kFilterConstruction"); DEFINE_bool(strict_capacity_limit, false, "Setting for " "LRUCacheOptions::strict_capacity_limit"); DEFINE_bool(quick, false, "Run more limited set of tests, fewer queries"); DEFINE_bool(best_case, false, "Run limited tests only for best-case"); DEFINE_bool(allow_bad_fp_rate, false, "Continue even if FP rate is bad"); DEFINE_bool(legend, false, "Print more information about interpreting results instead of " "running tests"); DEFINE_uint32(runs, 1, "Number of times to rebuild and run benchmark tests"); void _always_assert_fail(int line, const char *file, const char *expr) { fprintf(stderr, "%s: %d: Assertion %s failed\n", file, line, expr); abort(); } #define ALWAYS_ASSERT(cond) \ ((cond) ? (void)0 : ::_always_assert_fail(__LINE__, __FILE__, #cond)) #ifndef NDEBUG // This could affect build times enough that we should not include it for // accurate speed tests #define PREDICT_FP_RATE #endif using ROCKSDB_NAMESPACE::Arena; using ROCKSDB_NAMESPACE::BlockContents; using ROCKSDB_NAMESPACE::BloomFilterPolicy; using ROCKSDB_NAMESPACE::BloomHash; using ROCKSDB_NAMESPACE::BloomLikeFilterPolicy; using ROCKSDB_NAMESPACE::BuiltinFilterBitsBuilder; using ROCKSDB_NAMESPACE::CachableEntry; using ROCKSDB_NAMESPACE::Cache; using ROCKSDB_NAMESPACE::CacheEntryRole; using ROCKSDB_NAMESPACE::CacheEntryRoleOptions; using ROCKSDB_NAMESPACE::EncodeFixed32; using ROCKSDB_NAMESPACE::Env; using ROCKSDB_NAMESPACE::FastRange32; using ROCKSDB_NAMESPACE::FilterBitsReader; using ROCKSDB_NAMESPACE::FilterBuildingContext; using ROCKSDB_NAMESPACE::FilterPolicy; using ROCKSDB_NAMESPACE::FullFilterBlockReader; using ROCKSDB_NAMESPACE::GetSliceHash; using ROCKSDB_NAMESPACE::GetSliceHash64; using ROCKSDB_NAMESPACE::Lower32of64; using ROCKSDB_NAMESPACE::LRUCacheOptions; using ROCKSDB_NAMESPACE::ParsedFullFilterBlock; using ROCKSDB_NAMESPACE::PlainTableBloomV1; using ROCKSDB_NAMESPACE::Random32; using ROCKSDB_NAMESPACE::Slice; using ROCKSDB_NAMESPACE::static_cast_with_check; using ROCKSDB_NAMESPACE::Status; using ROCKSDB_NAMESPACE::StderrLogger; using ROCKSDB_NAMESPACE::mock::MockBlockBasedTableTester; struct KeyMaker { KeyMaker(size_t avg_size) : smallest_size_(avg_size - (FLAGS_vary_key_size_log2_interval >= 30 ? 2 : 0)), buf_size_(avg_size + 11), // pad to vary key size and alignment buf_(new char[buf_size_]) { memset(buf_.get(), 0, buf_size_); assert(smallest_size_ > 8); } size_t smallest_size_; size_t buf_size_; std::unique_ptr buf_; // Returns a unique(-ish) key based on the given parameter values. Each // call returns a Slice from the same buffer so previously returned // Slices should be considered invalidated. Slice Get(uint32_t filter_num, uint32_t val_num) { size_t start = FLAGS_vary_key_alignment ? val_num % 4 : 0; size_t len = smallest_size_; if (FLAGS_vary_key_size_log2_interval < 30) { // To get range [avg_size - 2, avg_size + 2] // use range [smallest_size, smallest_size + 4] len += FastRange32( (val_num >> FLAGS_vary_key_size_log2_interval) * 1234567891, 5); } char *data = buf_.get() + start; // Populate key data such that all data makes it into a key of at // least 8 bytes. We also don't want all the within-filter key // variance confined to a contiguous 32 bits, because then a 32 bit // hash function can "cheat" the false positive rate by // approximating a perfect hash. EncodeFixed32(data, val_num); EncodeFixed32(data + 4, filter_num + val_num); // ensure clearing leftovers from different alignment EncodeFixed32(data + 8, 0); return Slice(data, len); } }; void PrintWarnings() { #if defined(__GNUC__) && !defined(__OPTIMIZE__) fprintf(stdout, "WARNING: Optimization is disabled: benchmarks unnecessarily slow\n"); #endif #ifndef NDEBUG fprintf(stdout, "WARNING: Assertions are enabled; benchmarks unnecessarily slow\n"); #endif } void PrintError(const char *error) { fprintf(stderr, "ERROR: %s\n", error); } struct FilterInfo { uint32_t filter_id_ = 0; std::unique_ptr owner_; Slice filter_; Status filter_construction_status = Status::OK(); uint32_t keys_added_ = 0; std::unique_ptr reader_; std::unique_ptr full_block_reader_; std::unique_ptr plain_table_bloom_; uint64_t outside_queries_ = 0; uint64_t false_positives_ = 0; }; enum TestMode { kSingleFilter, kBatchPrepared, kBatchUnprepared, kFiftyOneFilter, kEightyTwentyFilter, kRandomFilter, }; static const std::vector allTestModes = { kSingleFilter, kBatchPrepared, kBatchUnprepared, kFiftyOneFilter, kEightyTwentyFilter, kRandomFilter, }; static const std::vector quickTestModes = { kSingleFilter, kRandomFilter, }; static const std::vector bestCaseTestModes = { kSingleFilter, }; const char *TestModeToString(TestMode tm) { switch (tm) { case kSingleFilter: return "Single filter"; case kBatchPrepared: return "Batched, prepared"; case kBatchUnprepared: return "Batched, unprepared"; case kFiftyOneFilter: return "Skewed 50% in 1%"; case kEightyTwentyFilter: return "Skewed 80% in 20%"; case kRandomFilter: return "Random filter"; } return "Bad TestMode"; } // Do just enough to keep some data dependence for the // compiler / CPU static uint32_t DryRunNoHash(Slice &s) { uint32_t sz = static_cast(s.size()); if (sz >= 4) { return sz + s.data()[3]; } else { return sz; } } static uint32_t DryRunHash32(Slice &s) { // Same perf characteristics as GetSliceHash() return BloomHash(s); } static uint32_t DryRunHash64(Slice &s) { return Lower32of64(GetSliceHash64(s)); } const std::shared_ptr &GetPolicy() { static std::shared_ptr policy; if (!policy) { policy = BloomLikeFilterPolicy::Create( BloomLikeFilterPolicy::GetAllFixedImpls().at(FLAGS_impl), FLAGS_bits_per_key); } return policy; } struct FilterBench : public MockBlockBasedTableTester { std::vector kms_; std::vector infos_; Random32 random_; std::ostringstream fp_rate_report_; Arena arena_; double m_queries_; StderrLogger stderr_logger_; FilterBench() : MockBlockBasedTableTester(GetPolicy()), random_(FLAGS_seed), m_queries_(0) { for (uint32_t i = 0; i < FLAGS_batch_size; ++i) { kms_.emplace_back(FLAGS_key_size < 8 ? 8 : FLAGS_key_size); } ioptions_.logger = &stderr_logger_; table_options_.optimize_filters_for_memory = FLAGS_optimize_filters_for_memory; table_options_.detect_filter_construct_corruption = FLAGS_detect_filter_construct_corruption; table_options_.cache_usage_options.options_overrides.insert( {CacheEntryRole::kFilterConstruction, {/*.charged = */ FLAGS_charge_filter_construction ? CacheEntryRoleOptions::Decision::kEnabled : CacheEntryRoleOptions::Decision::kDisabled}}); if (FLAGS_charge_filter_construction) { table_options_.no_block_cache = false; LRUCacheOptions lo; lo.capacity = FLAGS_block_cache_capacity_MB * 1024 * 1024; lo.num_shard_bits = 0; // 2^0 shard lo.strict_capacity_limit = FLAGS_strict_capacity_limit; std::shared_ptr cache(NewLRUCache(lo)); table_options_.block_cache = cache; } } void Go(); double RandomQueryTest(uint32_t inside_threshold, bool dry_run, TestMode mode); }; void FilterBench::Go() { if (FLAGS_use_plain_table_bloom && FLAGS_use_full_block_reader) { throw std::runtime_error( "Can't combine -use_plain_table_bloom and -use_full_block_reader"); } if (FLAGS_use_plain_table_bloom) { if (FLAGS_impl > 1) { throw std::runtime_error( "-impl must currently be >= 0 and <= 1 for Plain table"); } } else { if (FLAGS_impl > 2) { throw std::runtime_error( "-impl must currently be >= 0 and <= 2 for Block-based table"); } } if (FLAGS_vary_key_count_ratio < 0.0 || FLAGS_vary_key_count_ratio > 1.0) { throw std::runtime_error("-vary_key_count_ratio must be >= 0.0 and <= 1.0"); } // For example, average_keys_per_filter = 100, vary_key_count_ratio = 0.1. // Varys up to +/- 10 keys. variance_range = 21 (generating value 0..20). // variance_offset = 10, so value - offset average value is always 0. const uint32_t variance_range = 1 + 2 * static_cast(FLAGS_vary_key_count_ratio * FLAGS_average_keys_per_filter); const uint32_t variance_offset = variance_range / 2; const std::vector &testModes = FLAGS_best_case ? bestCaseTestModes : FLAGS_quick ? quickTestModes : allTestModes; m_queries_ = FLAGS_m_queries; double working_mem_size_mb = FLAGS_working_mem_size_mb; if (FLAGS_quick) { m_queries_ /= 7.0; } else if (FLAGS_best_case) { m_queries_ /= 3.0; working_mem_size_mb /= 10.0; } std::cout << "Building..." << std::endl; std::unique_ptr builder; size_t total_memory_used = 0; size_t total_size = 0; size_t total_keys_added = 0; #ifdef PREDICT_FP_RATE double weighted_predicted_fp_rate = 0.0; #endif size_t max_total_keys; size_t max_mem; if (FLAGS_m_keys_total_max > 0) { max_total_keys = static_cast(1000000 * FLAGS_m_keys_total_max); max_mem = SIZE_MAX; } else { max_total_keys = SIZE_MAX; max_mem = static_cast(1024 * 1024 * working_mem_size_mb); } ROCKSDB_NAMESPACE::StopWatchNano timer( ROCKSDB_NAMESPACE::SystemClock::Default().get(), true); infos_.clear(); while ((working_mem_size_mb == 0 || total_size < max_mem) && total_keys_added < max_total_keys) { uint32_t filter_id = random_.Next(); uint32_t keys_to_add = FLAGS_average_keys_per_filter + FastRange32(random_.Next(), variance_range) - variance_offset; if (max_total_keys - total_keys_added < keys_to_add) { keys_to_add = static_cast(max_total_keys - total_keys_added); } infos_.emplace_back(); FilterInfo &info = infos_.back(); info.filter_id_ = filter_id; info.keys_added_ = keys_to_add; if (FLAGS_use_plain_table_bloom) { info.plain_table_bloom_.reset(new PlainTableBloomV1()); info.plain_table_bloom_->SetTotalBits( &arena_, static_cast(keys_to_add * FLAGS_bits_per_key), FLAGS_impl, 0 /*huge_page*/, nullptr /*logger*/); for (uint32_t i = 0; i < keys_to_add; ++i) { uint32_t hash = GetSliceHash(kms_[0].Get(filter_id, i)); info.plain_table_bloom_->AddHash(hash); } info.filter_ = info.plain_table_bloom_->GetRawData(); } else { if (!builder) { builder.reset( static_cast_with_check(GetBuilder())); } for (uint32_t i = 0; i < keys_to_add; ++i) { builder->AddKey(kms_[0].Get(filter_id, i)); } info.filter_ = builder->Finish(&info.owner_, &info.filter_construction_status); if (info.filter_construction_status.ok()) { info.filter_construction_status = builder->MaybePostVerify(info.filter_); } if (!info.filter_construction_status.ok()) { PrintError(info.filter_construction_status.ToString().c_str()); } #ifdef PREDICT_FP_RATE weighted_predicted_fp_rate += keys_to_add * builder->EstimatedFpRate(keys_to_add, info.filter_.size()); #endif if (FLAGS_new_builder) { builder.reset(); } info.reader_.reset( table_options_.filter_policy->GetFilterBitsReader(info.filter_)); CachableEntry block( new ParsedFullFilterBlock(table_options_.filter_policy.get(), BlockContents(info.filter_)), nullptr /* cache */, nullptr /* cache_handle */, true /* own_value */); info.full_block_reader_.reset( new FullFilterBlockReader(table_.get(), std::move(block))); } total_size += info.filter_.size(); #ifdef ROCKSDB_MALLOC_USABLE_SIZE total_memory_used += malloc_usable_size(const_cast(info.filter_.data())); #endif // ROCKSDB_MALLOC_USABLE_SIZE total_keys_added += keys_to_add; } uint64_t elapsed_nanos = timer.ElapsedNanos(); double ns = double(elapsed_nanos) / total_keys_added; std::cout << "Build avg ns/key: " << ns << std::endl; std::cout << "Number of filters: " << infos_.size() << std::endl; std::cout << "Total size (MB): " << total_size / 1024.0 / 1024.0 << std::endl; if (total_memory_used > 0) { std::cout << "Reported total allocated memory (MB): " << total_memory_used / 1024.0 / 1024.0 << std::endl; std::cout << "Reported internal fragmentation: " << (total_memory_used - total_size) * 100.0 / total_size << "%" << std::endl; } double bpk = total_size * 8.0 / total_keys_added; std::cout << "Bits/key stored: " << bpk << std::endl; #ifdef PREDICT_FP_RATE std::cout << "Predicted FP rate %: " << 100.0 * (weighted_predicted_fp_rate / total_keys_added) << std::endl; #endif if (!FLAGS_quick && !FLAGS_best_case) { double tolerable_rate = std::pow(2.0, -(bpk - 1.0) / (1.4 + bpk / 50.0)); std::cout << "Best possible FP rate %: " << 100.0 * std::pow(2.0, -bpk) << std::endl; std::cout << "Tolerable FP rate %: " << 100.0 * tolerable_rate << std::endl; std::cout << "----------------------------" << std::endl; std::cout << "Verifying..." << std::endl; uint32_t outside_q_per_f = static_cast(m_queries_ * 1000000 / infos_.size()); uint64_t fps = 0; for (uint32_t i = 0; i < infos_.size(); ++i) { FilterInfo &info = infos_[i]; for (uint32_t j = 0; j < info.keys_added_; ++j) { if (FLAGS_use_plain_table_bloom) { uint32_t hash = GetSliceHash(kms_[0].Get(info.filter_id_, j)); ALWAYS_ASSERT(info.plain_table_bloom_->MayContainHash(hash)); } else { ALWAYS_ASSERT( info.reader_->MayMatch(kms_[0].Get(info.filter_id_, j))); } } for (uint32_t j = 0; j < outside_q_per_f; ++j) { if (FLAGS_use_plain_table_bloom) { uint32_t hash = GetSliceHash(kms_[0].Get(info.filter_id_, j | 0x80000000)); fps += info.plain_table_bloom_->MayContainHash(hash); } else { fps += info.reader_->MayMatch( kms_[0].Get(info.filter_id_, j | 0x80000000)); } } } std::cout << " No FNs :)" << std::endl; double prelim_rate = double(fps) / outside_q_per_f / infos_.size(); std::cout << " Prelim FP rate %: " << (100.0 * prelim_rate) << std::endl; if (!FLAGS_allow_bad_fp_rate) { ALWAYS_ASSERT(prelim_rate < tolerable_rate); } } std::cout << "----------------------------" << std::endl; std::cout << "Mixed inside/outside queries..." << std::endl; // 50% each inside and outside uint32_t inside_threshold = UINT32_MAX / 2; for (TestMode tm : testModes) { random_.Seed(FLAGS_seed + 1); double f = RandomQueryTest(inside_threshold, /*dry_run*/ false, tm); random_.Seed(FLAGS_seed + 1); double d = RandomQueryTest(inside_threshold, /*dry_run*/ true, tm); std::cout << " " << TestModeToString(tm) << " net ns/op: " << (f - d) << std::endl; } if (!FLAGS_quick) { std::cout << "----------------------------" << std::endl; std::cout << "Inside queries (mostly)..." << std::endl; // Do about 95% inside queries rather than 100% so that branch predictor // can't give itself an artifically crazy advantage. inside_threshold = UINT32_MAX / 20 * 19; for (TestMode tm : testModes) { random_.Seed(FLAGS_seed + 1); double f = RandomQueryTest(inside_threshold, /*dry_run*/ false, tm); random_.Seed(FLAGS_seed + 1); double d = RandomQueryTest(inside_threshold, /*dry_run*/ true, tm); std::cout << " " << TestModeToString(tm) << " net ns/op: " << (f - d) << std::endl; } std::cout << "----------------------------" << std::endl; std::cout << "Outside queries (mostly)..." << std::endl; // Do about 95% outside queries rather than 100% so that branch predictor // can't give itself an artifically crazy advantage. inside_threshold = UINT32_MAX / 20; for (TestMode tm : testModes) { random_.Seed(FLAGS_seed + 2); double f = RandomQueryTest(inside_threshold, /*dry_run*/ false, tm); random_.Seed(FLAGS_seed + 2); double d = RandomQueryTest(inside_threshold, /*dry_run*/ true, tm); std::cout << " " << TestModeToString(tm) << " net ns/op: " << (f - d) << std::endl; } } std::cout << fp_rate_report_.str(); std::cout << "----------------------------" << std::endl; std::cout << "Done. (For more info, run with -legend or -help.)" << std::endl; } double FilterBench::RandomQueryTest(uint32_t inside_threshold, bool dry_run, TestMode mode) { for (auto &info : infos_) { info.outside_queries_ = 0; info.false_positives_ = 0; } auto dry_run_hash_fn = DryRunNoHash; if (!FLAGS_net_includes_hashing) { if (FLAGS_impl == 0 || FLAGS_use_plain_table_bloom) { dry_run_hash_fn = DryRunHash32; } else { dry_run_hash_fn = DryRunHash64; } } uint32_t num_infos = static_cast(infos_.size()); uint32_t dry_run_hash = 0; uint64_t max_queries = static_cast(m_queries_ * 1000000 + 0.50); // Some filters may be considered secondary in order to implement skewed // queries. num_primary_filters is the number that are to be treated as // equal, and any remainder will be treated as secondary. uint32_t num_primary_filters = num_infos; // The proportion (when divided by 2^32 - 1) of filter queries going to // the primary filters (default = all). The remainder of queries are // against secondary filters. uint32_t primary_filter_threshold = 0xffffffff; if (mode == kSingleFilter) { // 100% of queries to 1 filter num_primary_filters = 1; } else if (mode == kFiftyOneFilter) { if (num_infos < 50) { return 0.0; // skip } // 50% of queries primary_filter_threshold /= 2; // to 1% of filters num_primary_filters = (num_primary_filters + 99) / 100; } else if (mode == kEightyTwentyFilter) { if (num_infos < 5) { return 0.0; // skip } // 80% of queries primary_filter_threshold = primary_filter_threshold / 5 * 4; // to 20% of filters num_primary_filters = (num_primary_filters + 4) / 5; } else if (mode == kRandomFilter) { if (num_infos == 1) { return 0.0; // skip } } uint32_t batch_size = 1; std::unique_ptr batch_slices; std::unique_ptr batch_slice_ptrs; std::unique_ptr batch_results; if (mode == kBatchPrepared || mode == kBatchUnprepared) { batch_size = static_cast(kms_.size()); } batch_slices.reset(new Slice[batch_size]); batch_slice_ptrs.reset(new Slice *[batch_size]); batch_results.reset(new bool[batch_size]); for (uint32_t i = 0; i < batch_size; ++i) { batch_results[i] = false; batch_slice_ptrs[i] = &batch_slices[i]; } ROCKSDB_NAMESPACE::StopWatchNano timer( ROCKSDB_NAMESPACE::SystemClock::Default().get(), true); for (uint64_t q = 0; q < max_queries; q += batch_size) { bool inside_this_time = random_.Next() <= inside_threshold; uint32_t filter_index; if (random_.Next() <= primary_filter_threshold) { filter_index = random_.Uniformish(num_primary_filters); } else { // secondary filter_index = num_primary_filters + random_.Uniformish(num_infos - num_primary_filters); } FilterInfo &info = infos_[filter_index]; for (uint32_t i = 0; i < batch_size; ++i) { if (inside_this_time) { batch_slices[i] = kms_[i].Get(info.filter_id_, random_.Uniformish(info.keys_added_)); } else { batch_slices[i] = kms_[i].Get(info.filter_id_, random_.Uniformish(info.keys_added_) | uint32_t{0x80000000}); info.outside_queries_++; } } // TODO: implement batched interface to full block reader // TODO: implement batched interface to plain table bloom if (mode == kBatchPrepared && !FLAGS_use_full_block_reader && !FLAGS_use_plain_table_bloom) { for (uint32_t i = 0; i < batch_size; ++i) { batch_results[i] = false; } if (dry_run) { for (uint32_t i = 0; i < batch_size; ++i) { batch_results[i] = true; dry_run_hash += dry_run_hash_fn(batch_slices[i]); } } else { info.reader_->MayMatch(batch_size, batch_slice_ptrs.get(), batch_results.get()); } for (uint32_t i = 0; i < batch_size; ++i) { if (inside_this_time) { ALWAYS_ASSERT(batch_results[i]); } else { info.false_positives_ += batch_results[i]; } } } else { for (uint32_t i = 0; i < batch_size; ++i) { bool may_match; if (FLAGS_use_plain_table_bloom) { if (dry_run) { dry_run_hash += dry_run_hash_fn(batch_slices[i]); may_match = true; } else { uint32_t hash = GetSliceHash(batch_slices[i]); may_match = info.plain_table_bloom_->MayContainHash(hash); } } else if (FLAGS_use_full_block_reader) { if (dry_run) { dry_run_hash += dry_run_hash_fn(batch_slices[i]); may_match = true; } else { may_match = info.full_block_reader_->KeyMayMatch( batch_slices[i], /*const_ikey_ptr=*/nullptr, /*get_context=*/nullptr, /*lookup_context=*/nullptr, ROCKSDB_NAMESPACE::ReadOptions()); } } else { if (dry_run) { dry_run_hash += dry_run_hash_fn(batch_slices[i]); may_match = true; } else { may_match = info.reader_->MayMatch(batch_slices[i]); } } if (inside_this_time) { ALWAYS_ASSERT(may_match); } else { info.false_positives_ += may_match; } } } } uint64_t elapsed_nanos = timer.ElapsedNanos(); double ns = double(elapsed_nanos) / max_queries; if (!FLAGS_quick) { if (dry_run) { // Printing part of hash prevents dry run components from being optimized // away by compiler std::cout << " Dry run (" << std::hex << (dry_run_hash & 0xfffff) << std::dec << ") "; } else { std::cout << " Gross filter "; } std::cout << "ns/op: " << ns << std::endl; } if (!dry_run) { fp_rate_report_.str(""); uint64_t q = 0; uint64_t fp = 0; double worst_fp_rate = 0.0; double best_fp_rate = 1.0; for (auto &info : infos_) { q += info.outside_queries_; fp += info.false_positives_; if (info.outside_queries_ > 0) { double fp_rate = double(info.false_positives_) / info.outside_queries_; worst_fp_rate = std::max(worst_fp_rate, fp_rate); best_fp_rate = std::min(best_fp_rate, fp_rate); } } fp_rate_report_ << " Average FP rate %: " << 100.0 * fp / q << std::endl; if (!FLAGS_quick && !FLAGS_best_case) { fp_rate_report_ << " Worst FP rate %: " << 100.0 * worst_fp_rate << std::endl; fp_rate_report_ << " Best FP rate %: " << 100.0 * best_fp_rate << std::endl; fp_rate_report_ << " Best possible bits/key: " << -std::log(double(fp) / q) / std::log(2.0) << std::endl; } } return ns; } int main(int argc, char **argv) { ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) + " [-quick] [OTHER OPTIONS]..."); ParseCommandLineFlags(&argc, &argv, true); PrintWarnings(); if (FLAGS_legend) { std::cout << "Legend:" << std::endl << " \"Inside\" - key that was added to filter" << std::endl << " \"Outside\" - key that was not added to filter" << std::endl << " \"FN\" - false negative query (must not happen)" << std::endl << " \"FP\" - false positive query (OK at low rate)" << std::endl << " \"Dry run\" - cost of testing and hashing overhead." << std::endl << " \"Gross filter\" - cost of filter queries including testing " << "\n and hashing overhead." << std::endl << " \"net\" - best estimate of time in filter operation, without " << "\n testing and hashing overhead (gross filter - dry run)" << std::endl << " \"ns/op\" - nanoseconds per operation (key query or add)" << std::endl << " \"Single filter\" - essentially minimum cost, assuming filter" << "\n fits easily in L1 CPU cache." << std::endl << " \"Batched, prepared\" - several queries at once against a" << "\n randomly chosen filter, using multi-query interface." << std::endl << " \"Batched, unprepared\" - similar, but using serial calls" << "\n to single query interface." << std::endl << " \"Random filter\" - a filter is chosen at random as target" << "\n of each query." << std::endl << " \"Skewed X% in Y%\" - like \"Random filter\" except Y% of" << "\n the filters are designated as \"hot\" and receive X%" << "\n of queries." << std::endl; } else { FilterBench b; for (uint32_t i = 0; i < FLAGS_runs; ++i) { b.Go(); FLAGS_seed += 100; b.random_.Seed(FLAGS_seed); } } return 0; } #endif // !defined(GFLAGS)