hash_table_bench.cc 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. // Copyright (c) 2013, 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(OS_WIN) && !defined(ROCKSDB_LITE)
  7. #ifndef GFLAGS
  8. #include <cstdio>
  9. int main() { fprintf(stderr, "Please install gflags to run tools\n"); }
  10. #else
  11. #include <atomic>
  12. #include <functional>
  13. #include <string>
  14. #include <unordered_map>
  15. #include <unistd.h>
  16. #include <sys/time.h>
  17. #include "port/port_posix.h"
  18. #include "rocksdb/env.h"
  19. #include "util/gflags_compat.h"
  20. #include "util/mutexlock.h"
  21. #include "util/random.h"
  22. #include "utilities/persistent_cache/hash_table.h"
  23. using std::string;
  24. DEFINE_int32(nsec, 10, "nsec");
  25. DEFINE_int32(nthread_write, 1, "insert %");
  26. DEFINE_int32(nthread_read, 1, "lookup %");
  27. DEFINE_int32(nthread_erase, 1, "erase %");
  28. namespace ROCKSDB_NAMESPACE {
  29. //
  30. // HashTableImpl interface
  31. //
  32. // Abstraction of a hash table implementation
  33. template <class Key, class Value>
  34. class HashTableImpl {
  35. public:
  36. virtual ~HashTableImpl() {}
  37. virtual bool Insert(const Key& key, const Value& val) = 0;
  38. virtual bool Erase(const Key& key) = 0;
  39. virtual bool Lookup(const Key& key, Value* val) = 0;
  40. };
  41. // HashTableBenchmark
  42. //
  43. // Abstraction to test a given hash table implementation. The test mostly
  44. // focus on insert, lookup and erase. The test can operate in test mode and
  45. // benchmark mode.
  46. class HashTableBenchmark {
  47. public:
  48. explicit HashTableBenchmark(HashTableImpl<size_t, std::string>* impl,
  49. const size_t sec = 10,
  50. const size_t nthread_write = 1,
  51. const size_t nthread_read = 1,
  52. const size_t nthread_erase = 1)
  53. : impl_(impl),
  54. sec_(sec),
  55. ninserts_(0),
  56. nreads_(0),
  57. nerases_(0),
  58. nerases_failed_(0),
  59. quit_(false) {
  60. Prepop();
  61. StartThreads(nthread_write, WriteMain);
  62. StartThreads(nthread_read, ReadMain);
  63. StartThreads(nthread_erase, EraseMain);
  64. uint64_t start = NowInMillSec();
  65. while (!quit_) {
  66. quit_ = NowInMillSec() - start > sec_ * 1000;
  67. /* sleep override */ sleep(1);
  68. }
  69. Env* env = Env::Default();
  70. env->WaitForJoin();
  71. if (sec_) {
  72. printf("Result \n");
  73. printf("====== \n");
  74. printf("insert/sec = %f \n", ninserts_ / static_cast<double>(sec_));
  75. printf("read/sec = %f \n", nreads_ / static_cast<double>(sec_));
  76. printf("erases/sec = %f \n", nerases_ / static_cast<double>(sec_));
  77. const uint64_t ops = ninserts_ + nreads_ + nerases_;
  78. printf("ops/sec = %f \n", ops / static_cast<double>(sec_));
  79. printf("erase fail = %d (%f%%)\n", static_cast<int>(nerases_failed_),
  80. static_cast<float>(nerases_failed_ / nerases_ * 100));
  81. printf("====== \n");
  82. }
  83. }
  84. void RunWrite() {
  85. while (!quit_) {
  86. size_t k = insert_key_++;
  87. std::string tmp(1000, k % 255);
  88. bool status = impl_->Insert(k, tmp);
  89. assert(status);
  90. ninserts_++;
  91. }
  92. }
  93. void RunRead() {
  94. Random64 rgen(time(nullptr));
  95. while (!quit_) {
  96. std::string s;
  97. size_t k = rgen.Next() % max_prepop_key;
  98. bool status = impl_->Lookup(k, &s);
  99. assert(status);
  100. assert(s == std::string(1000, k % 255));
  101. nreads_++;
  102. }
  103. }
  104. void RunErase() {
  105. while (!quit_) {
  106. size_t k = erase_key_++;
  107. bool status = impl_->Erase(k);
  108. nerases_failed_ += !status;
  109. nerases_++;
  110. }
  111. }
  112. private:
  113. // Start threads for a given function
  114. void StartThreads(const size_t n, void (*fn)(void*)) {
  115. Env* env = Env::Default();
  116. for (size_t i = 0; i < n; ++i) {
  117. env->StartThread(fn, this);
  118. }
  119. }
  120. // Prepop the hash table with 1M keys
  121. void Prepop() {
  122. for (size_t i = 0; i < max_prepop_key; ++i) {
  123. bool status = impl_->Insert(i, std::string(1000, i % 255));
  124. assert(status);
  125. }
  126. erase_key_ = insert_key_ = max_prepop_key;
  127. for (size_t i = 0; i < 10 * max_prepop_key; ++i) {
  128. bool status = impl_->Insert(insert_key_++, std::string(1000, 'x'));
  129. assert(status);
  130. }
  131. }
  132. static uint64_t NowInMillSec() {
  133. timeval tv;
  134. gettimeofday(&tv, /*tz=*/nullptr);
  135. return tv.tv_sec * 1000 + tv.tv_usec / 1000;
  136. }
  137. //
  138. // Wrapper functions for thread entry
  139. //
  140. static void WriteMain(void* args) {
  141. reinterpret_cast<HashTableBenchmark*>(args)->RunWrite();
  142. }
  143. static void ReadMain(void* args) {
  144. reinterpret_cast<HashTableBenchmark*>(args)->RunRead();
  145. }
  146. static void EraseMain(void* args) {
  147. reinterpret_cast<HashTableBenchmark*>(args)->RunErase();
  148. }
  149. HashTableImpl<size_t, std::string>* impl_; // Implementation to test
  150. const size_t sec_; // Test time
  151. const size_t max_prepop_key = 1ULL * 1024 * 1024; // Max prepop key
  152. std::atomic<size_t> insert_key_; // Last inserted key
  153. std::atomic<size_t> erase_key_; // Erase key
  154. std::atomic<size_t> ninserts_; // Number of inserts
  155. std::atomic<size_t> nreads_; // Number of reads
  156. std::atomic<size_t> nerases_; // Number of erases
  157. std::atomic<size_t> nerases_failed_; // Number of erases failed
  158. bool quit_; // Should the threads quit ?
  159. };
  160. //
  161. // SimpleImpl
  162. // Lock safe unordered_map implementation
  163. class SimpleImpl : public HashTableImpl<size_t, string> {
  164. public:
  165. bool Insert(const size_t& key, const string& val) override {
  166. WriteLock _(&rwlock_);
  167. map_.insert(make_pair(key, val));
  168. return true;
  169. }
  170. bool Erase(const size_t& key) override {
  171. WriteLock _(&rwlock_);
  172. auto it = map_.find(key);
  173. if (it == map_.end()) {
  174. return false;
  175. }
  176. map_.erase(it);
  177. return true;
  178. }
  179. bool Lookup(const size_t& key, string* val) override {
  180. ReadLock _(&rwlock_);
  181. auto it = map_.find(key);
  182. if (it != map_.end()) {
  183. *val = it->second;
  184. }
  185. return it != map_.end();
  186. }
  187. private:
  188. port::RWMutex rwlock_;
  189. std::unordered_map<size_t, string> map_;
  190. };
  191. //
  192. // GranularLockImpl
  193. // Thread safe custom RocksDB implementation of hash table with granular
  194. // locking
  195. class GranularLockImpl : public HashTableImpl<size_t, string> {
  196. public:
  197. bool Insert(const size_t& key, const string& val) override {
  198. Node n(key, val);
  199. return impl_.Insert(n);
  200. }
  201. bool Erase(const size_t& key) override {
  202. Node n(key, string());
  203. return impl_.Erase(n, nullptr);
  204. }
  205. bool Lookup(const size_t& key, string* val) override {
  206. Node n(key, string());
  207. port::RWMutex* rlock;
  208. bool status = impl_.Find(n, &n, &rlock);
  209. if (status) {
  210. ReadUnlock _(rlock);
  211. *val = n.val_;
  212. }
  213. return status;
  214. }
  215. private:
  216. struct Node {
  217. explicit Node(const size_t key, const string& val) : key_(key), val_(val) {}
  218. size_t key_ = 0;
  219. string val_;
  220. };
  221. struct Hash {
  222. uint64_t operator()(const Node& node) {
  223. return std::hash<uint64_t>()(node.key_);
  224. }
  225. };
  226. struct Equal {
  227. bool operator()(const Node& lhs, const Node& rhs) {
  228. return lhs.key_ == rhs.key_;
  229. }
  230. };
  231. HashTable<Node, Hash, Equal> impl_;
  232. };
  233. } // namespace ROCKSDB_NAMESPACE
  234. //
  235. // main
  236. //
  237. int main(int argc, char** argv) {
  238. GFLAGS_NAMESPACE::SetUsageMessage(std::string("\nUSAGE:\n") +
  239. std::string(argv[0]) + " [OPTIONS]...");
  240. GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, false);
  241. //
  242. // Micro benchmark unordered_map
  243. //
  244. printf("Micro benchmarking std::unordered_map \n");
  245. {
  246. ROCKSDB_NAMESPACE::SimpleImpl impl;
  247. ROCKSDB_NAMESPACE::HashTableBenchmark _(
  248. &impl, FLAGS_nsec, FLAGS_nthread_write, FLAGS_nthread_read,
  249. FLAGS_nthread_erase);
  250. }
  251. //
  252. // Micro benchmark scalable hash table
  253. //
  254. printf("Micro benchmarking scalable hash map \n");
  255. {
  256. ROCKSDB_NAMESPACE::GranularLockImpl impl;
  257. ROCKSDB_NAMESPACE::HashTableBenchmark _(
  258. &impl, FLAGS_nsec, FLAGS_nthread_write, FLAGS_nthread_read,
  259. FLAGS_nthread_erase);
  260. }
  261. return 0;
  262. }
  263. #endif // #ifndef GFLAGS
  264. #else
  265. int main(int /*argc*/, char** /*argv*/) { return 0; }
  266. #endif