hash_table_bench.cc 8.1 KB

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