dynamic_bloom_test.cc 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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. #ifndef GFLAGS
  6. #include <cstdio>
  7. int main() {
  8. fprintf(stderr, "Please install gflags to run this test... Skipping...\n");
  9. return 0;
  10. }
  11. #else
  12. #include <algorithm>
  13. #include <atomic>
  14. #include <cinttypes>
  15. #include <functional>
  16. #include <memory>
  17. #include <thread>
  18. #include <vector>
  19. #include "dynamic_bloom.h"
  20. #include "logging/logging.h"
  21. #include "memory/arena.h"
  22. #include "port/port.h"
  23. #include "test_util/testharness.h"
  24. #include "test_util/testutil.h"
  25. #include "util/gflags_compat.h"
  26. #include "util/stop_watch.h"
  27. using GFLAGS_NAMESPACE::ParseCommandLineFlags;
  28. DEFINE_int32(bits_per_key, 10, "");
  29. DEFINE_int32(num_probes, 6, "");
  30. DEFINE_bool(enable_perf, false, "");
  31. namespace ROCKSDB_NAMESPACE {
  32. struct KeyMaker {
  33. uint64_t a;
  34. uint64_t b;
  35. // Sequential, within a hash function block
  36. inline Slice Seq(uint64_t i) {
  37. a = i;
  38. return Slice(reinterpret_cast<char *>(&a), sizeof(a));
  39. }
  40. // Not quite sequential, varies across hash function blocks
  41. inline Slice Nonseq(uint64_t i) {
  42. a = i;
  43. b = i * 123;
  44. return Slice(reinterpret_cast<char *>(this), sizeof(*this));
  45. }
  46. inline Slice Key(uint64_t i, bool nonseq) {
  47. return nonseq ? Nonseq(i) : Seq(i);
  48. }
  49. };
  50. class DynamicBloomTest : public testing::Test {};
  51. TEST_F(DynamicBloomTest, EmptyFilter) {
  52. Arena arena;
  53. DynamicBloom bloom1(&arena, 100, 2);
  54. ASSERT_TRUE(!bloom1.MayContain("hello"));
  55. ASSERT_TRUE(!bloom1.MayContain("world"));
  56. DynamicBloom bloom2(&arena, CACHE_LINE_SIZE * 8 * 2 - 1, 2);
  57. ASSERT_TRUE(!bloom2.MayContain("hello"));
  58. ASSERT_TRUE(!bloom2.MayContain("world"));
  59. }
  60. TEST_F(DynamicBloomTest, Small) {
  61. Arena arena;
  62. DynamicBloom bloom1(&arena, 100, 2);
  63. bloom1.Add("hello");
  64. bloom1.Add("world");
  65. ASSERT_TRUE(bloom1.MayContain("hello"));
  66. ASSERT_TRUE(bloom1.MayContain("world"));
  67. ASSERT_TRUE(!bloom1.MayContain("x"));
  68. ASSERT_TRUE(!bloom1.MayContain("foo"));
  69. DynamicBloom bloom2(&arena, CACHE_LINE_SIZE * 8 * 2 - 1, 2);
  70. bloom2.Add("hello");
  71. bloom2.Add("world");
  72. ASSERT_TRUE(bloom2.MayContain("hello"));
  73. ASSERT_TRUE(bloom2.MayContain("world"));
  74. ASSERT_TRUE(!bloom2.MayContain("x"));
  75. ASSERT_TRUE(!bloom2.MayContain("foo"));
  76. }
  77. TEST_F(DynamicBloomTest, SmallConcurrentAdd) {
  78. Arena arena;
  79. DynamicBloom bloom1(&arena, 100, 2);
  80. bloom1.AddConcurrently("hello");
  81. bloom1.AddConcurrently("world");
  82. ASSERT_TRUE(bloom1.MayContain("hello"));
  83. ASSERT_TRUE(bloom1.MayContain("world"));
  84. ASSERT_TRUE(!bloom1.MayContain("x"));
  85. ASSERT_TRUE(!bloom1.MayContain("foo"));
  86. DynamicBloom bloom2(&arena, CACHE_LINE_SIZE * 8 * 2 - 1, 2);
  87. bloom2.AddConcurrently("hello");
  88. bloom2.AddConcurrently("world");
  89. ASSERT_TRUE(bloom2.MayContain("hello"));
  90. ASSERT_TRUE(bloom2.MayContain("world"));
  91. ASSERT_TRUE(!bloom2.MayContain("x"));
  92. ASSERT_TRUE(!bloom2.MayContain("foo"));
  93. }
  94. static uint32_t NextNum(uint32_t num) {
  95. if (num < 10) {
  96. num += 1;
  97. } else if (num < 100) {
  98. num += 10;
  99. } else if (num < 1000) {
  100. num += 100;
  101. } else {
  102. num = num * 26 / 10;
  103. }
  104. return num;
  105. }
  106. TEST_F(DynamicBloomTest, VaryingLengths) {
  107. KeyMaker km;
  108. // Count number of filters that significantly exceed the false positive rate
  109. int mediocre_filters = 0;
  110. int good_filters = 0;
  111. uint32_t num_probes = static_cast<uint32_t>(FLAGS_num_probes);
  112. fprintf(stderr, "bits_per_key: %d num_probes: %d\n", FLAGS_bits_per_key,
  113. num_probes);
  114. // NB: FP rate impact of 32-bit hash is noticeable starting around 10M keys.
  115. // But that effect is hidden if using sequential keys (unique hashes).
  116. for (bool nonseq : {false, true}) {
  117. const uint32_t max_num = FLAGS_enable_perf ? 40000000 : 400000;
  118. for (uint32_t num = 1; num <= max_num; num = NextNum(num)) {
  119. uint32_t bloom_bits = 0;
  120. Arena arena;
  121. bloom_bits = num * FLAGS_bits_per_key;
  122. DynamicBloom bloom(&arena, bloom_bits, num_probes);
  123. for (uint64_t i = 0; i < num; i++) {
  124. bloom.Add(km.Key(i, nonseq));
  125. ASSERT_TRUE(bloom.MayContain(km.Key(i, nonseq)));
  126. }
  127. // All added keys must match
  128. for (uint64_t i = 0; i < num; i++) {
  129. ASSERT_TRUE(bloom.MayContain(km.Key(i, nonseq)));
  130. }
  131. // Check false positive rate
  132. int result = 0;
  133. for (uint64_t i = 0; i < 30000; i++) {
  134. if (bloom.MayContain(km.Key(i + 1000000000, nonseq))) {
  135. result++;
  136. }
  137. }
  138. double rate = result / 30000.0;
  139. fprintf(stderr,
  140. "False positives (%s keys): "
  141. "%5.2f%% @ num = %6u, bloom_bits = %6u\n",
  142. nonseq ? "nonseq" : "seq", rate * 100.0, num, bloom_bits);
  143. if (rate > 0.0125)
  144. mediocre_filters++; // Allowed, but not too often
  145. else
  146. good_filters++;
  147. }
  148. }
  149. fprintf(stderr, "Filters: %d good, %d mediocre\n", good_filters,
  150. mediocre_filters);
  151. ASSERT_LE(mediocre_filters, good_filters / 25);
  152. }
  153. TEST_F(DynamicBloomTest, perf) {
  154. KeyMaker km;
  155. StopWatchNano timer(Env::Default());
  156. uint32_t num_probes = static_cast<uint32_t>(FLAGS_num_probes);
  157. if (!FLAGS_enable_perf) {
  158. return;
  159. }
  160. for (uint32_t m = 1; m <= 8; ++m) {
  161. Arena arena;
  162. const uint32_t num_keys = m * 8 * 1024 * 1024;
  163. fprintf(stderr, "testing %" PRIu32 "M keys\n", m * 8);
  164. DynamicBloom std_bloom(&arena, num_keys * 10, num_probes);
  165. timer.Start();
  166. for (uint64_t i = 1; i <= num_keys; ++i) {
  167. std_bloom.Add(km.Seq(i));
  168. }
  169. uint64_t elapsed = timer.ElapsedNanos();
  170. fprintf(stderr, "dynamic bloom, avg add latency %3g\n",
  171. static_cast<double>(elapsed) / num_keys);
  172. uint32_t count = 0;
  173. timer.Start();
  174. for (uint64_t i = 1; i <= num_keys; ++i) {
  175. if (std_bloom.MayContain(km.Seq(i))) {
  176. ++count;
  177. }
  178. }
  179. ASSERT_EQ(count, num_keys);
  180. elapsed = timer.ElapsedNanos();
  181. assert(count > 0);
  182. fprintf(stderr, "dynamic bloom, avg query latency %3g\n",
  183. static_cast<double>(elapsed) / count);
  184. }
  185. }
  186. TEST_F(DynamicBloomTest, concurrent_with_perf) {
  187. uint32_t num_probes = static_cast<uint32_t>(FLAGS_num_probes);
  188. uint32_t m_limit = FLAGS_enable_perf ? 8 : 1;
  189. uint32_t num_threads = 4;
  190. std::vector<port::Thread> threads;
  191. // NB: Uses sequential keys for speed, but that hides the FP rate
  192. // impact of 32-bit hash, which is noticeable starting around 10M keys
  193. // when they vary across hashing blocks.
  194. for (uint32_t m = 1; m <= m_limit; ++m) {
  195. Arena arena;
  196. const uint32_t num_keys = m * 8 * 1024 * 1024;
  197. fprintf(stderr, "testing %" PRIu32 "M keys\n", m * 8);
  198. DynamicBloom std_bloom(&arena, num_keys * 10, num_probes);
  199. std::atomic<uint64_t> elapsed(0);
  200. std::function<void(size_t)> adder([&](size_t t) {
  201. KeyMaker km;
  202. StopWatchNano timer(Env::Default());
  203. timer.Start();
  204. for (uint64_t i = 1 + t; i <= num_keys; i += num_threads) {
  205. std_bloom.AddConcurrently(km.Seq(i));
  206. }
  207. elapsed += timer.ElapsedNanos();
  208. });
  209. for (size_t t = 0; t < num_threads; ++t) {
  210. threads.emplace_back(adder, t);
  211. }
  212. while (threads.size() > 0) {
  213. threads.back().join();
  214. threads.pop_back();
  215. }
  216. fprintf(stderr,
  217. "dynamic bloom, avg parallel add latency %3g"
  218. " nanos/key\n",
  219. static_cast<double>(elapsed) / num_threads / num_keys);
  220. elapsed = 0;
  221. std::function<void(size_t)> hitter([&](size_t t) {
  222. KeyMaker km;
  223. StopWatchNano timer(Env::Default());
  224. timer.Start();
  225. for (uint64_t i = 1 + t; i <= num_keys; i += num_threads) {
  226. bool f = std_bloom.MayContain(km.Seq(i));
  227. ASSERT_TRUE(f);
  228. }
  229. elapsed += timer.ElapsedNanos();
  230. });
  231. for (size_t t = 0; t < num_threads; ++t) {
  232. threads.emplace_back(hitter, t);
  233. }
  234. while (threads.size() > 0) {
  235. threads.back().join();
  236. threads.pop_back();
  237. }
  238. fprintf(stderr,
  239. "dynamic bloom, avg parallel hit latency %3g"
  240. " nanos/key\n",
  241. static_cast<double>(elapsed) / num_threads / num_keys);
  242. elapsed = 0;
  243. std::atomic<uint32_t> false_positives(0);
  244. std::function<void(size_t)> misser([&](size_t t) {
  245. KeyMaker km;
  246. StopWatchNano timer(Env::Default());
  247. timer.Start();
  248. for (uint64_t i = num_keys + 1 + t; i <= 2 * num_keys; i += num_threads) {
  249. bool f = std_bloom.MayContain(km.Seq(i));
  250. if (f) {
  251. ++false_positives;
  252. }
  253. }
  254. elapsed += timer.ElapsedNanos();
  255. });
  256. for (size_t t = 0; t < num_threads; ++t) {
  257. threads.emplace_back(misser, t);
  258. }
  259. while (threads.size() > 0) {
  260. threads.back().join();
  261. threads.pop_back();
  262. }
  263. fprintf(stderr,
  264. "dynamic bloom, avg parallel miss latency %3g"
  265. " nanos/key, %f%% false positive rate\n",
  266. static_cast<double>(elapsed) / num_threads / num_keys,
  267. false_positives.load() * 100.0 / num_keys);
  268. }
  269. }
  270. } // namespace ROCKSDB_NAMESPACE
  271. int main(int argc, char** argv) {
  272. ::testing::InitGoogleTest(&argc, argv);
  273. ParseCommandLineFlags(&argc, &argv, true);
  274. return RUN_ALL_TESTS();
  275. }
  276. #endif // GFLAGS