1 //  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2 //  This source code is licensed under both the GPLv2 (found in the
3 //  COPYING file in the root directory) and Apache 2.0 License
4 //  (found in the LICENSE.Apache file in the root directory).
5 
6 #if !defined(GFLAGS) || defined(ROCKSDB_LITE)
7 #include <cstdio>
main()8 int main() {
9   fprintf(stderr, "filter_bench requires gflags and !ROCKSDB_LITE\n");
10   return 1;
11 }
12 #else
13 
14 #include <cinttypes>
15 #include <iostream>
16 #include <sstream>
17 #include <vector>
18 
19 #include "memory/arena.h"
20 #include "port/port.h"
21 #include "port/stack_trace.h"
22 #include "table/block_based/filter_policy_internal.h"
23 #include "table/block_based/full_filter_block.h"
24 #include "table/block_based/mock_block_based_table.h"
25 #include "table/plain/plain_table_bloom.h"
26 #include "util/gflags_compat.h"
27 #include "util/hash.h"
28 #include "util/random.h"
29 #include "util/stderr_logger.h"
30 #include "util/stop_watch.h"
31 
32 using GFLAGS_NAMESPACE::ParseCommandLineFlags;
33 using GFLAGS_NAMESPACE::RegisterFlagValidator;
34 using GFLAGS_NAMESPACE::SetUsageMessage;
35 
36 DEFINE_uint32(seed, 0, "Seed for random number generators");
37 
38 DEFINE_double(working_mem_size_mb, 200,
39               "MB of memory to get up to among all filters, unless "
40               "m_keys_total_max is specified.");
41 
42 DEFINE_uint32(average_keys_per_filter, 10000,
43               "Average number of keys per filter");
44 
45 DEFINE_double(vary_key_count_ratio, 0.4,
46               "Vary number of keys by up to +/- vary_key_count_ratio * "
47               "average_keys_per_filter.");
48 
49 DEFINE_uint32(key_size, 24, "Average number of bytes for each key");
50 
51 DEFINE_bool(vary_key_alignment, true,
52             "Whether to vary key alignment (default: at least 32-bit "
53             "alignment)");
54 
55 DEFINE_uint32(vary_key_size_log2_interval, 5,
56               "Use same key size 2^n times, then change. Key size varies from "
57               "-2 to +2 bytes vs. average, unless n>=30 to fix key size.");
58 
59 DEFINE_uint32(batch_size, 8, "Number of keys to group in each batch");
60 
61 DEFINE_double(bits_per_key, 10.0, "Bits per key setting for filters");
62 
63 DEFINE_double(m_queries, 200, "Millions of queries for each test mode");
64 
65 DEFINE_double(m_keys_total_max, 0,
66               "Maximum total keys added to filters, in millions. "
67               "0 (default) disables. Non-zero overrides working_mem_size_mb "
68               "option.");
69 
70 DEFINE_bool(use_full_block_reader, false,
71             "Use FullFilterBlockReader interface rather than FilterBitsReader");
72 
73 DEFINE_bool(use_plain_table_bloom, false,
74             "Use PlainTableBloom structure and interface rather than "
75             "FilterBitsReader/FullFilterBlockReader");
76 
77 DEFINE_bool(new_builder, false,
78             "Whether to create a new builder for each new filter");
79 
80 DEFINE_uint32(impl, 0,
81               "Select filter implementation. Without -use_plain_table_bloom:"
82               "0 = full filter, 1 = block-based filter. With "
83               "-use_plain_table_bloom: 0 = no locality, 1 = locality.");
84 
85 DEFINE_bool(net_includes_hashing, false,
86             "Whether query net ns/op times should include hashing. "
87             "(if not, dry run will include hashing) "
88             "(build times always include hashing)");
89 
90 DEFINE_bool(quick, false, "Run more limited set of tests, fewer queries");
91 
92 DEFINE_bool(best_case, false, "Run limited tests only for best-case");
93 
94 DEFINE_bool(allow_bad_fp_rate, false, "Continue even if FP rate is bad");
95 
96 DEFINE_bool(legend, false,
97             "Print more information about interpreting results instead of "
98             "running tests");
99 
100 DEFINE_uint32(runs, 1, "Number of times to rebuild and run benchmark tests");
101 
_always_assert_fail(int line,const char * file,const char * expr)102 void _always_assert_fail(int line, const char *file, const char *expr) {
103   fprintf(stderr, "%s: %d: Assertion %s failed\n", file, line, expr);
104   abort();
105 }
106 
107 #define ALWAYS_ASSERT(cond) \
108   ((cond) ? (void)0 : ::_always_assert_fail(__LINE__, __FILE__, #cond))
109 
110 #ifndef NDEBUG
111 // This could affect build times enough that we should not include it for
112 // accurate speed tests
113 #define PREDICT_FP_RATE
114 #endif
115 
116 using ROCKSDB_NAMESPACE::Arena;
117 using ROCKSDB_NAMESPACE::BlockContents;
118 using ROCKSDB_NAMESPACE::BloomFilterPolicy;
119 using ROCKSDB_NAMESPACE::BloomHash;
120 using ROCKSDB_NAMESPACE::BuiltinFilterBitsBuilder;
121 using ROCKSDB_NAMESPACE::CachableEntry;
122 using ROCKSDB_NAMESPACE::EncodeFixed32;
123 using ROCKSDB_NAMESPACE::fastrange32;
124 using ROCKSDB_NAMESPACE::FilterBitsReader;
125 using ROCKSDB_NAMESPACE::FilterBuildingContext;
126 using ROCKSDB_NAMESPACE::FullFilterBlockReader;
127 using ROCKSDB_NAMESPACE::GetSliceHash;
128 using ROCKSDB_NAMESPACE::GetSliceHash64;
129 using ROCKSDB_NAMESPACE::Lower32of64;
130 using ROCKSDB_NAMESPACE::ParsedFullFilterBlock;
131 using ROCKSDB_NAMESPACE::PlainTableBloomV1;
132 using ROCKSDB_NAMESPACE::Random32;
133 using ROCKSDB_NAMESPACE::Slice;
134 using ROCKSDB_NAMESPACE::StderrLogger;
135 using ROCKSDB_NAMESPACE::mock::MockBlockBasedTableTester;
136 
137 struct KeyMaker {
KeyMakerKeyMaker138   KeyMaker(size_t avg_size)
139       : smallest_size_(avg_size -
140                        (FLAGS_vary_key_size_log2_interval >= 30 ? 2 : 0)),
141         buf_size_(avg_size + 11),  // pad to vary key size and alignment
142         buf_(new char[buf_size_]) {
143     memset(buf_.get(), 0, buf_size_);
144     assert(smallest_size_ > 8);
145   }
146   size_t smallest_size_;
147   size_t buf_size_;
148   std::unique_ptr<char[]> buf_;
149 
150   // Returns a unique(-ish) key based on the given parameter values. Each
151   // call returns a Slice from the same buffer so previously returned
152   // Slices should be considered invalidated.
GetKeyMaker153   Slice Get(uint32_t filter_num, uint32_t val_num) {
154     size_t start = FLAGS_vary_key_alignment ? val_num % 4 : 0;
155     size_t len = smallest_size_;
156     if (FLAGS_vary_key_size_log2_interval < 30) {
157       // To get range [avg_size - 2, avg_size + 2]
158       // use range [smallest_size, smallest_size + 4]
159       len += fastrange32(
160           (val_num >> FLAGS_vary_key_size_log2_interval) * 1234567891, 5);
161     }
162     char * data = buf_.get() + start;
163     // Populate key data such that all data makes it into a key of at
164     // least 8 bytes. We also don't want all the within-filter key
165     // variance confined to a contiguous 32 bits, because then a 32 bit
166     // hash function can "cheat" the false positive rate by
167     // approximating a perfect hash.
168     EncodeFixed32(data, val_num);
169     EncodeFixed32(data + 4, filter_num + val_num);
170     // ensure clearing leftovers from different alignment
171     EncodeFixed32(data + 8, 0);
172     return Slice(data, len);
173   }
174 };
175 
PrintWarnings()176 void PrintWarnings() {
177 #if defined(__GNUC__) && !defined(__OPTIMIZE__)
178   fprintf(stdout,
179           "WARNING: Optimization is disabled: benchmarks unnecessarily slow\n");
180 #endif
181 #ifndef NDEBUG
182   fprintf(stdout,
183           "WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
184 #endif
185 }
186 
187 struct FilterInfo {
188   uint32_t filter_id_ = 0;
189   std::unique_ptr<const char[]> owner_;
190   Slice filter_;
191   uint32_t keys_added_ = 0;
192   std::unique_ptr<FilterBitsReader> reader_;
193   std::unique_ptr<FullFilterBlockReader> full_block_reader_;
194   std::unique_ptr<PlainTableBloomV1> plain_table_bloom_;
195   uint64_t outside_queries_ = 0;
196   uint64_t false_positives_ = 0;
197 };
198 
199 enum TestMode {
200   kSingleFilter,
201   kBatchPrepared,
202   kBatchUnprepared,
203   kFiftyOneFilter,
204   kEightyTwentyFilter,
205   kRandomFilter,
206 };
207 
208 static const std::vector<TestMode> allTestModes = {
209     kSingleFilter,   kBatchPrepared,      kBatchUnprepared,
210     kFiftyOneFilter, kEightyTwentyFilter, kRandomFilter,
211 };
212 
213 static const std::vector<TestMode> quickTestModes = {
214     kSingleFilter,
215     kRandomFilter,
216 };
217 
218 static const std::vector<TestMode> bestCaseTestModes = {
219     kSingleFilter,
220 };
221 
TestModeToString(TestMode tm)222 const char *TestModeToString(TestMode tm) {
223   switch (tm) {
224     case kSingleFilter:
225       return "Single filter";
226     case kBatchPrepared:
227       return "Batched, prepared";
228     case kBatchUnprepared:
229       return "Batched, unprepared";
230     case kFiftyOneFilter:
231       return "Skewed 50% in 1%";
232     case kEightyTwentyFilter:
233       return "Skewed 80% in 20%";
234     case kRandomFilter:
235       return "Random filter";
236   }
237   return "Bad TestMode";
238 }
239 
240 // Do just enough to keep some data dependence for the
241 // compiler / CPU
DryRunNoHash(Slice & s)242 static uint32_t DryRunNoHash(Slice &s) {
243   uint32_t sz = static_cast<uint32_t>(s.size());
244   if (sz >= 4) {
245     return sz + s.data()[3];
246   } else {
247     return sz;
248   }
249 }
250 
DryRunHash32(Slice & s)251 static uint32_t DryRunHash32(Slice &s) {
252   // Same perf characteristics as GetSliceHash()
253   return BloomHash(s);
254 }
255 
DryRunHash64(Slice & s)256 static uint32_t DryRunHash64(Slice &s) {
257   return Lower32of64(GetSliceHash64(s));
258 }
259 
260 struct FilterBench : public MockBlockBasedTableTester {
261   std::vector<KeyMaker> kms_;
262   std::vector<FilterInfo> infos_;
263   Random32 random_;
264   std::ostringstream fp_rate_report_;
265   Arena arena_;
266   StderrLogger stderr_logger_;
267   double m_queries_;
268 
FilterBenchFilterBench269   FilterBench()
270       : MockBlockBasedTableTester(new BloomFilterPolicy(
271             FLAGS_bits_per_key,
272             static_cast<BloomFilterPolicy::Mode>(FLAGS_impl))),
273         random_(FLAGS_seed),
274         m_queries_(0) {
275     for (uint32_t i = 0; i < FLAGS_batch_size; ++i) {
276       kms_.emplace_back(FLAGS_key_size < 8 ? 8 : FLAGS_key_size);
277     }
278     ioptions_.info_log = &stderr_logger_;
279   }
280 
281   void Go();
282 
283   double RandomQueryTest(uint32_t inside_threshold, bool dry_run,
284                          TestMode mode);
285 };
286 
Go()287 void FilterBench::Go() {
288   if (FLAGS_use_plain_table_bloom && FLAGS_use_full_block_reader) {
289     throw std::runtime_error(
290         "Can't combine -use_plain_table_bloom and -use_full_block_reader");
291   }
292   if (FLAGS_use_plain_table_bloom) {
293     if (FLAGS_impl > 1) {
294       throw std::runtime_error(
295           "-impl must currently be >= 0 and <= 1 for Plain table");
296     }
297   } else {
298     if (FLAGS_impl == 1) {
299       throw std::runtime_error(
300           "Block-based filter not currently supported by filter_bench");
301     }
302     if (FLAGS_impl > 2) {
303       throw std::runtime_error(
304           "-impl must currently be 0 or 2 for Block-based table");
305     }
306   }
307 
308   if (FLAGS_vary_key_count_ratio < 0.0 || FLAGS_vary_key_count_ratio > 1.0) {
309     throw std::runtime_error("-vary_key_count_ratio must be >= 0.0 and <= 1.0");
310   }
311 
312   // For example, average_keys_per_filter = 100, vary_key_count_ratio = 0.1.
313   // Varys up to +/- 10 keys. variance_range = 21 (generating value 0..20).
314   // variance_offset = 10, so value - offset average value is always 0.
315   const uint32_t variance_range =
316       1 + 2 * static_cast<uint32_t>(FLAGS_vary_key_count_ratio *
317                                     FLAGS_average_keys_per_filter);
318   const uint32_t variance_offset = variance_range / 2;
319 
320   const std::vector<TestMode> &testModes =
321       FLAGS_best_case ? bestCaseTestModes
322                       : FLAGS_quick ? quickTestModes : allTestModes;
323 
324   m_queries_ = FLAGS_m_queries;
325   double working_mem_size_mb = FLAGS_working_mem_size_mb;
326   if (FLAGS_quick) {
327     m_queries_ /= 7.0;
328   } else if (FLAGS_best_case) {
329     m_queries_ /= 3.0;
330     working_mem_size_mb /= 10.0;
331   }
332 
333   std::cout << "Building..." << std::endl;
334 
335   std::unique_ptr<BuiltinFilterBitsBuilder> builder;
336 
337   size_t total_memory_used = 0;
338   size_t total_keys_added = 0;
339 #ifdef PREDICT_FP_RATE
340   double weighted_predicted_fp_rate = 0.0;
341 #endif
342   size_t max_total_keys;
343   size_t max_mem;
344   if (FLAGS_m_keys_total_max > 0) {
345     max_total_keys = static_cast<size_t>(1000000 * FLAGS_m_keys_total_max);
346     max_mem = SIZE_MAX;
347   } else {
348     max_total_keys = SIZE_MAX;
349     max_mem = static_cast<size_t>(1024 * 1024 * working_mem_size_mb);
350   }
351 
352   ROCKSDB_NAMESPACE::StopWatchNano timer(ROCKSDB_NAMESPACE::Env::Default(),
353                                          true);
354 
355   infos_.clear();
356   while ((working_mem_size_mb == 0 || total_memory_used < max_mem) &&
357          total_keys_added < max_total_keys) {
358     uint32_t filter_id = random_.Next();
359     uint32_t keys_to_add = FLAGS_average_keys_per_filter +
360                            fastrange32(random_.Next(), variance_range) -
361                            variance_offset;
362     if (max_total_keys - total_keys_added < keys_to_add) {
363       keys_to_add = static_cast<uint32_t>(max_total_keys - total_keys_added);
364     }
365     infos_.emplace_back();
366     FilterInfo &info = infos_.back();
367     info.filter_id_ = filter_id;
368     info.keys_added_ = keys_to_add;
369     if (FLAGS_use_plain_table_bloom) {
370       info.plain_table_bloom_.reset(new PlainTableBloomV1());
371       info.plain_table_bloom_->SetTotalBits(
372           &arena_, static_cast<uint32_t>(keys_to_add * FLAGS_bits_per_key),
373           FLAGS_impl, 0 /*huge_page*/, nullptr /*logger*/);
374       for (uint32_t i = 0; i < keys_to_add; ++i) {
375         uint32_t hash = GetSliceHash(kms_[0].Get(filter_id, i));
376         info.plain_table_bloom_->AddHash(hash);
377       }
378       info.filter_ = info.plain_table_bloom_->GetRawData();
379     } else {
380       if (!builder) {
381         builder.reset(&dynamic_cast<BuiltinFilterBitsBuilder &>(*GetBuilder()));
382       }
383       for (uint32_t i = 0; i < keys_to_add; ++i) {
384         builder->AddKey(kms_[0].Get(filter_id, i));
385       }
386       info.filter_ = builder->Finish(&info.owner_);
387 #ifdef PREDICT_FP_RATE
388       weighted_predicted_fp_rate +=
389           keys_to_add *
390           builder->EstimatedFpRate(keys_to_add, info.filter_.size());
391 #endif
392       if (FLAGS_new_builder) {
393         builder.reset();
394       }
395       info.reader_.reset(
396           table_options_.filter_policy->GetFilterBitsReader(info.filter_));
397       CachableEntry<ParsedFullFilterBlock> block(
398           new ParsedFullFilterBlock(table_options_.filter_policy.get(),
399                                     BlockContents(info.filter_)),
400           nullptr /* cache */, nullptr /* cache_handle */,
401           true /* own_value */);
402       info.full_block_reader_.reset(
403           new FullFilterBlockReader(table_.get(), std::move(block)));
404     }
405     total_memory_used += info.filter_.size();
406     total_keys_added += keys_to_add;
407   }
408 
409   uint64_t elapsed_nanos = timer.ElapsedNanos();
410   double ns = double(elapsed_nanos) / total_keys_added;
411   std::cout << "Build avg ns/key: " << ns << std::endl;
412   std::cout << "Number of filters: " << infos_.size() << std::endl;
413   std::cout << "Total memory (MB): " << total_memory_used / 1024.0 / 1024.0
414             << std::endl;
415 
416   double bpk = total_memory_used * 8.0 / total_keys_added;
417   std::cout << "Bits/key actual: " << bpk << std::endl;
418 #ifdef PREDICT_FP_RATE
419   std::cout << "Predicted FP rate %: "
420             << 100.0 * (weighted_predicted_fp_rate / total_keys_added)
421             << std::endl;
422 #endif
423   if (!FLAGS_quick && !FLAGS_best_case) {
424     double tolerable_rate = std::pow(2.0, -(bpk - 1.0) / (1.4 + bpk / 50.0));
425     std::cout << "Best possible FP rate %: " << 100.0 * std::pow(2.0, -bpk)
426               << std::endl;
427     std::cout << "Tolerable FP rate %: " << 100.0 * tolerable_rate << std::endl;
428 
429     std::cout << "----------------------------" << std::endl;
430     std::cout << "Verifying..." << std::endl;
431 
432     uint32_t outside_q_per_f =
433         static_cast<uint32_t>(m_queries_ * 1000000 / infos_.size());
434     uint64_t fps = 0;
435     for (uint32_t i = 0; i < infos_.size(); ++i) {
436       FilterInfo &info = infos_[i];
437       for (uint32_t j = 0; j < info.keys_added_; ++j) {
438         if (FLAGS_use_plain_table_bloom) {
439           uint32_t hash = GetSliceHash(kms_[0].Get(info.filter_id_, j));
440           ALWAYS_ASSERT(info.plain_table_bloom_->MayContainHash(hash));
441         } else {
442           ALWAYS_ASSERT(
443               info.reader_->MayMatch(kms_[0].Get(info.filter_id_, j)));
444         }
445       }
446       for (uint32_t j = 0; j < outside_q_per_f; ++j) {
447         if (FLAGS_use_plain_table_bloom) {
448           uint32_t hash =
449               GetSliceHash(kms_[0].Get(info.filter_id_, j | 0x80000000));
450           fps += info.plain_table_bloom_->MayContainHash(hash);
451         } else {
452           fps += info.reader_->MayMatch(
453               kms_[0].Get(info.filter_id_, j | 0x80000000));
454         }
455       }
456     }
457     std::cout << " No FNs :)" << std::endl;
458     double prelim_rate = double(fps) / outside_q_per_f / infos_.size();
459     std::cout << " Prelim FP rate %: " << (100.0 * prelim_rate) << std::endl;
460 
461     if (!FLAGS_allow_bad_fp_rate) {
462       ALWAYS_ASSERT(prelim_rate < tolerable_rate);
463     }
464   }
465 
466   std::cout << "----------------------------" << std::endl;
467   std::cout << "Mixed inside/outside queries..." << std::endl;
468   // 50% each inside and outside
469   uint32_t inside_threshold = UINT32_MAX / 2;
470   for (TestMode tm : testModes) {
471     random_.Seed(FLAGS_seed + 1);
472     double f = RandomQueryTest(inside_threshold, /*dry_run*/ false, tm);
473     random_.Seed(FLAGS_seed + 1);
474     double d = RandomQueryTest(inside_threshold, /*dry_run*/ true, tm);
475     std::cout << "  " << TestModeToString(tm) << " net ns/op: " << (f - d)
476               << std::endl;
477   }
478 
479   if (!FLAGS_quick) {
480     std::cout << "----------------------------" << std::endl;
481     std::cout << "Inside queries (mostly)..." << std::endl;
482     // Do about 95% inside queries rather than 100% so that branch predictor
483     // can't give itself an artifically crazy advantage.
484     inside_threshold = UINT32_MAX / 20 * 19;
485     for (TestMode tm : testModes) {
486       random_.Seed(FLAGS_seed + 1);
487       double f = RandomQueryTest(inside_threshold, /*dry_run*/ false, tm);
488       random_.Seed(FLAGS_seed + 1);
489       double d = RandomQueryTest(inside_threshold, /*dry_run*/ true, tm);
490       std::cout << "  " << TestModeToString(tm) << " net ns/op: " << (f - d)
491                 << std::endl;
492     }
493 
494     std::cout << "----------------------------" << std::endl;
495     std::cout << "Outside queries (mostly)..." << std::endl;
496     // Do about 95% outside queries rather than 100% so that branch predictor
497     // can't give itself an artifically crazy advantage.
498     inside_threshold = UINT32_MAX / 20;
499     for (TestMode tm : testModes) {
500       random_.Seed(FLAGS_seed + 2);
501       double f = RandomQueryTest(inside_threshold, /*dry_run*/ false, tm);
502       random_.Seed(FLAGS_seed + 2);
503       double d = RandomQueryTest(inside_threshold, /*dry_run*/ true, tm);
504       std::cout << "  " << TestModeToString(tm) << " net ns/op: " << (f - d)
505                 << std::endl;
506     }
507   }
508   std::cout << fp_rate_report_.str();
509 
510   std::cout << "----------------------------" << std::endl;
511   std::cout << "Done. (For more info, run with -legend or -help.)" << std::endl;
512 }
513 
RandomQueryTest(uint32_t inside_threshold,bool dry_run,TestMode mode)514 double FilterBench::RandomQueryTest(uint32_t inside_threshold, bool dry_run,
515                                     TestMode mode) {
516   for (auto &info : infos_) {
517     info.outside_queries_ = 0;
518     info.false_positives_ = 0;
519   }
520 
521   auto dry_run_hash_fn = DryRunNoHash;
522   if (!FLAGS_net_includes_hashing) {
523     if (FLAGS_impl < 2 || FLAGS_use_plain_table_bloom) {
524       dry_run_hash_fn = DryRunHash32;
525     } else {
526       dry_run_hash_fn = DryRunHash64;
527     }
528   }
529 
530   uint32_t num_infos = static_cast<uint32_t>(infos_.size());
531   uint32_t dry_run_hash = 0;
532   uint64_t max_queries = static_cast<uint64_t>(m_queries_ * 1000000 + 0.50);
533   // Some filters may be considered secondary in order to implement skewed
534   // queries. num_primary_filters is the number that are to be treated as
535   // equal, and any remainder will be treated as secondary.
536   uint32_t num_primary_filters = num_infos;
537   // The proportion (when divided by 2^32 - 1) of filter queries going to
538   // the primary filters (default = all). The remainder of queries are
539   // against secondary filters.
540   uint32_t primary_filter_threshold = 0xffffffff;
541   if (mode == kSingleFilter) {
542     // 100% of queries to 1 filter
543     num_primary_filters = 1;
544   } else if (mode == kFiftyOneFilter) {
545     // 50% of queries
546     primary_filter_threshold /= 2;
547     // to 1% of filters
548     num_primary_filters = (num_primary_filters + 99) / 100;
549   } else if (mode == kEightyTwentyFilter) {
550     // 80% of queries
551     primary_filter_threshold = primary_filter_threshold / 5 * 4;
552     // to 20% of filters
553     num_primary_filters = (num_primary_filters + 4) / 5;
554   }
555   uint32_t batch_size = 1;
556   std::unique_ptr<Slice[]> batch_slices;
557   std::unique_ptr<Slice *[]> batch_slice_ptrs;
558   std::unique_ptr<bool[]> batch_results;
559   if (mode == kBatchPrepared || mode == kBatchUnprepared) {
560     batch_size = static_cast<uint32_t>(kms_.size());
561   }
562 
563   batch_slices.reset(new Slice[batch_size]);
564   batch_slice_ptrs.reset(new Slice *[batch_size]);
565   batch_results.reset(new bool[batch_size]);
566   for (uint32_t i = 0; i < batch_size; ++i) {
567     batch_results[i] = false;
568     batch_slice_ptrs[i] = &batch_slices[i];
569   }
570 
571   ROCKSDB_NAMESPACE::StopWatchNano timer(ROCKSDB_NAMESPACE::Env::Default(),
572                                          true);
573 
574   for (uint64_t q = 0; q < max_queries; q += batch_size) {
575     bool inside_this_time = random_.Next() <= inside_threshold;
576 
577     uint32_t filter_index;
578     if (random_.Next() <= primary_filter_threshold) {
579       filter_index = random_.Uniformish(num_primary_filters);
580     } else {
581       // secondary
582       filter_index = num_primary_filters +
583                      random_.Uniformish(num_infos - num_primary_filters);
584     }
585     FilterInfo &info = infos_[filter_index];
586     for (uint32_t i = 0; i < batch_size; ++i) {
587       if (inside_this_time) {
588         batch_slices[i] =
589             kms_[i].Get(info.filter_id_, random_.Uniformish(info.keys_added_));
590       } else {
591         batch_slices[i] =
592             kms_[i].Get(info.filter_id_, random_.Uniformish(info.keys_added_) |
593                                              uint32_t{0x80000000});
594         info.outside_queries_++;
595       }
596     }
597     // TODO: implement batched interface to full block reader
598     // TODO: implement batched interface to plain table bloom
599     if (mode == kBatchPrepared && !FLAGS_use_full_block_reader &&
600         !FLAGS_use_plain_table_bloom) {
601       for (uint32_t i = 0; i < batch_size; ++i) {
602         batch_results[i] = false;
603       }
604       if (dry_run) {
605         for (uint32_t i = 0; i < batch_size; ++i) {
606           batch_results[i] = true;
607           dry_run_hash += dry_run_hash_fn(batch_slices[i]);
608         }
609       } else {
610         info.reader_->MayMatch(batch_size, batch_slice_ptrs.get(),
611                                batch_results.get());
612       }
613       for (uint32_t i = 0; i < batch_size; ++i) {
614         if (inside_this_time) {
615           ALWAYS_ASSERT(batch_results[i]);
616         } else {
617           info.false_positives_ += batch_results[i];
618         }
619       }
620     } else {
621       for (uint32_t i = 0; i < batch_size; ++i) {
622         bool may_match;
623         if (FLAGS_use_plain_table_bloom) {
624           if (dry_run) {
625             dry_run_hash += dry_run_hash_fn(batch_slices[i]);
626             may_match = true;
627           } else {
628             uint32_t hash = GetSliceHash(batch_slices[i]);
629             may_match = info.plain_table_bloom_->MayContainHash(hash);
630           }
631         } else if (FLAGS_use_full_block_reader) {
632           if (dry_run) {
633             dry_run_hash += dry_run_hash_fn(batch_slices[i]);
634             may_match = true;
635           } else {
636             may_match = info.full_block_reader_->KeyMayMatch(
637                 batch_slices[i],
638                 /*prefix_extractor=*/nullptr,
639                 /*block_offset=*/ROCKSDB_NAMESPACE::kNotValid,
640                 /*no_io=*/false, /*const_ikey_ptr=*/nullptr,
641                 /*get_context=*/nullptr,
642                 /*lookup_context=*/nullptr);
643           }
644         } else {
645           if (dry_run) {
646             dry_run_hash += dry_run_hash_fn(batch_slices[i]);
647             may_match = true;
648           } else {
649             may_match = info.reader_->MayMatch(batch_slices[i]);
650           }
651         }
652         if (inside_this_time) {
653           ALWAYS_ASSERT(may_match);
654         } else {
655           info.false_positives_ += may_match;
656         }
657       }
658     }
659   }
660 
661   uint64_t elapsed_nanos = timer.ElapsedNanos();
662   double ns = double(elapsed_nanos) / max_queries;
663 
664   if (!FLAGS_quick) {
665     if (dry_run) {
666       // Printing part of hash prevents dry run components from being optimized
667       // away by compiler
668       std::cout << "    Dry run (" << std::hex << (dry_run_hash & 0xfffff)
669                 << std::dec << ") ";
670     } else {
671       std::cout << "    Gross filter    ";
672     }
673     std::cout << "ns/op: " << ns << std::endl;
674   }
675 
676   if (!dry_run) {
677     fp_rate_report_.str("");
678     uint64_t q = 0;
679     uint64_t fp = 0;
680     double worst_fp_rate = 0.0;
681     double best_fp_rate = 1.0;
682     for (auto &info : infos_) {
683       q += info.outside_queries_;
684       fp += info.false_positives_;
685       if (info.outside_queries_ > 0) {
686         double fp_rate = double(info.false_positives_) / info.outside_queries_;
687         worst_fp_rate = std::max(worst_fp_rate, fp_rate);
688         best_fp_rate = std::min(best_fp_rate, fp_rate);
689       }
690     }
691     fp_rate_report_ << "    Average FP rate %: " << 100.0 * fp / q << std::endl;
692     if (!FLAGS_quick && !FLAGS_best_case) {
693       fp_rate_report_ << "    Worst   FP rate %: " << 100.0 * worst_fp_rate
694                       << std::endl;
695       fp_rate_report_ << "    Best    FP rate %: " << 100.0 * best_fp_rate
696                       << std::endl;
697       fp_rate_report_ << "    Best possible bits/key: "
698                       << -std::log(double(fp) / q) / std::log(2.0) << std::endl;
699     }
700   }
701   return ns;
702 }
703 
main(int argc,char ** argv)704 int main(int argc, char **argv) {
705   ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
706   SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
707                   " [-quick] [OTHER OPTIONS]...");
708   ParseCommandLineFlags(&argc, &argv, true);
709 
710   PrintWarnings();
711 
712   if (FLAGS_legend) {
713     std::cout
714         << "Legend:" << std::endl
715         << "  \"Inside\" - key that was added to filter" << std::endl
716         << "  \"Outside\" - key that was not added to filter" << std::endl
717         << "  \"FN\" - false negative query (must not happen)" << std::endl
718         << "  \"FP\" - false positive query (OK at low rate)" << std::endl
719         << "  \"Dry run\" - cost of testing and hashing overhead." << std::endl
720         << "  \"Gross filter\" - cost of filter queries including testing "
721         << "\n     and hashing overhead." << std::endl
722         << "  \"net\" - best estimate of time in filter operation, without "
723         << "\n     testing and hashing overhead (gross filter - dry run)"
724         << std::endl
725         << "  \"ns/op\" - nanoseconds per operation (key query or add)"
726         << std::endl
727         << "  \"Single filter\" - essentially minimum cost, assuming filter"
728         << "\n     fits easily in L1 CPU cache." << std::endl
729         << "  \"Batched, prepared\" - several queries at once against a"
730         << "\n     randomly chosen filter, using multi-query interface."
731         << std::endl
732         << "  \"Batched, unprepared\" - similar, but using serial calls"
733         << "\n     to single query interface." << std::endl
734         << "  \"Random filter\" - a filter is chosen at random as target"
735         << "\n     of each query." << std::endl
736         << "  \"Skewed X% in Y%\" - like \"Random filter\" except Y% of"
737         << "\n      the filters are designated as \"hot\" and receive X%"
738         << "\n      of queries." << std::endl;
739   } else {
740     FilterBench b;
741     for (uint32_t i = 0; i < FLAGS_runs; ++i) {
742       b.Go();
743       FLAGS_seed += 100;
744       b.random_.Seed(FLAGS_seed);
745     }
746   }
747 
748   return 0;
749 }
750 
751 #endif  // !defined(GFLAGS) || defined(ROCKSDB_LITE)
752