db_bench_tree_db.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file. See the AUTHORS file for names of contributors.
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <kcpolydb.h>
  7. #include "util/histogram.h"
  8. #include "util/random.h"
  9. #include "util/testutil.h"
  10. // Comma-separated list of operations to run in the specified order
  11. // Actual benchmarks:
  12. //
  13. // fillseq -- write N values in sequential key order in async mode
  14. // fillrandom -- write N values in random key order in async mode
  15. // overwrite -- overwrite N values in random key order in async mode
  16. // fillseqsync -- write N/100 values in sequential key order in sync mode
  17. // fillrandsync -- write N/100 values in random key order in sync mode
  18. // fillrand100K -- write N/1000 100K values in random order in async mode
  19. // fillseq100K -- write N/1000 100K values in seq order in async mode
  20. // readseq -- read N times sequentially
  21. // readseq100K -- read N/1000 100K values in sequential order in async mode
  22. // readrand100K -- read N/1000 100K values in sequential order in async mode
  23. // readrandom -- read N times in random order
  24. static const char* FLAGS_benchmarks =
  25. "fillseq,"
  26. "fillseqsync,"
  27. "fillrandsync,"
  28. "fillrandom,"
  29. "overwrite,"
  30. "readrandom,"
  31. "readseq,"
  32. "fillrand100K,"
  33. "fillseq100K,"
  34. "readseq100K,"
  35. "readrand100K,"
  36. ;
  37. // Number of key/values to place in database
  38. static int FLAGS_num = 1000000;
  39. // Number of read operations to do. If negative, do FLAGS_num reads.
  40. static int FLAGS_reads = -1;
  41. // Size of each value
  42. static int FLAGS_value_size = 100;
  43. // Arrange to generate values that shrink to this fraction of
  44. // their original size after compression
  45. static double FLAGS_compression_ratio = 0.5;
  46. // Print histogram of operation timings
  47. static bool FLAGS_histogram = false;
  48. // Cache size. Default 4 MB
  49. static int FLAGS_cache_size = 4194304;
  50. // Page size. Default 1 KB
  51. static int FLAGS_page_size = 1024;
  52. // If true, do not destroy the existing database. If you set this
  53. // flag and also specify a benchmark that wants a fresh database, that
  54. // benchmark will fail.
  55. static bool FLAGS_use_existing_db = false;
  56. // Compression flag. If true, compression is on. If false, compression
  57. // is off.
  58. static bool FLAGS_compression = true;
  59. // Use the db with the following name.
  60. static const char* FLAGS_db = nullptr;
  61. inline
  62. static void DBSynchronize(kyotocabinet::TreeDB* db_)
  63. {
  64. // Synchronize will flush writes to disk
  65. if (!db_->synchronize()) {
  66. fprintf(stderr, "synchronize error: %s\n", db_->error().name());
  67. }
  68. }
  69. namespace leveldb {
  70. // Helper for quickly generating random data.
  71. namespace {
  72. class RandomGenerator {
  73. private:
  74. std::string data_;
  75. int pos_;
  76. public:
  77. RandomGenerator() {
  78. // We use a limited amount of data over and over again and ensure
  79. // that it is larger than the compression window (32KB), and also
  80. // large enough to serve all typical value sizes we want to write.
  81. Random rnd(301);
  82. std::string piece;
  83. while (data_.size() < 1048576) {
  84. // Add a short fragment that is as compressible as specified
  85. // by FLAGS_compression_ratio.
  86. test::CompressibleString(&rnd, FLAGS_compression_ratio, 100, &piece);
  87. data_.append(piece);
  88. }
  89. pos_ = 0;
  90. }
  91. Slice Generate(int len) {
  92. if (pos_ + len > data_.size()) {
  93. pos_ = 0;
  94. assert(len < data_.size());
  95. }
  96. pos_ += len;
  97. return Slice(data_.data() + pos_ - len, len);
  98. }
  99. };
  100. static Slice TrimSpace(Slice s) {
  101. int start = 0;
  102. while (start < s.size() && isspace(s[start])) {
  103. start++;
  104. }
  105. int limit = s.size();
  106. while (limit > start && isspace(s[limit-1])) {
  107. limit--;
  108. }
  109. return Slice(s.data() + start, limit - start);
  110. }
  111. } // namespace
  112. class Benchmark {
  113. private:
  114. kyotocabinet::TreeDB* db_;
  115. int db_num_;
  116. int num_;
  117. int reads_;
  118. double start_;
  119. double last_op_finish_;
  120. int64_t bytes_;
  121. std::string message_;
  122. Histogram hist_;
  123. RandomGenerator gen_;
  124. Random rand_;
  125. kyotocabinet::LZOCompressor<kyotocabinet::LZO::RAW> comp_;
  126. // State kept for progress messages
  127. int done_;
  128. int next_report_; // When to report next
  129. void PrintHeader() {
  130. const int kKeySize = 16;
  131. PrintEnvironment();
  132. fprintf(stdout, "Keys: %d bytes each\n", kKeySize);
  133. fprintf(stdout, "Values: %d bytes each (%d bytes after compression)\n",
  134. FLAGS_value_size,
  135. static_cast<int>(FLAGS_value_size * FLAGS_compression_ratio + 0.5));
  136. fprintf(stdout, "Entries: %d\n", num_);
  137. fprintf(stdout, "RawSize: %.1f MB (estimated)\n",
  138. ((static_cast<int64_t>(kKeySize + FLAGS_value_size) * num_)
  139. / 1048576.0));
  140. fprintf(stdout, "FileSize: %.1f MB (estimated)\n",
  141. (((kKeySize + FLAGS_value_size * FLAGS_compression_ratio) * num_)
  142. / 1048576.0));
  143. PrintWarnings();
  144. fprintf(stdout, "------------------------------------------------\n");
  145. }
  146. void PrintWarnings() {
  147. #if defined(__GNUC__) && !defined(__OPTIMIZE__)
  148. fprintf(stdout,
  149. "WARNING: Optimization is disabled: benchmarks unnecessarily slow\n"
  150. );
  151. #endif
  152. #ifndef NDEBUG
  153. fprintf(stdout,
  154. "WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
  155. #endif
  156. }
  157. void PrintEnvironment() {
  158. fprintf(stderr, "Kyoto Cabinet: version %s, lib ver %d, lib rev %d\n",
  159. kyotocabinet::VERSION, kyotocabinet::LIBVER, kyotocabinet::LIBREV);
  160. #if defined(__linux)
  161. time_t now = time(nullptr);
  162. fprintf(stderr, "Date: %s", ctime(&now)); // ctime() adds newline
  163. FILE* cpuinfo = fopen("/proc/cpuinfo", "r");
  164. if (cpuinfo != nullptr) {
  165. char line[1000];
  166. int num_cpus = 0;
  167. std::string cpu_type;
  168. std::string cache_size;
  169. while (fgets(line, sizeof(line), cpuinfo) != nullptr) {
  170. const char* sep = strchr(line, ':');
  171. if (sep == nullptr) {
  172. continue;
  173. }
  174. Slice key = TrimSpace(Slice(line, sep - 1 - line));
  175. Slice val = TrimSpace(Slice(sep + 1));
  176. if (key == "model name") {
  177. ++num_cpus;
  178. cpu_type = val.ToString();
  179. } else if (key == "cache size") {
  180. cache_size = val.ToString();
  181. }
  182. }
  183. fclose(cpuinfo);
  184. fprintf(stderr, "CPU: %d * %s\n", num_cpus, cpu_type.c_str());
  185. fprintf(stderr, "CPUCache: %s\n", cache_size.c_str());
  186. }
  187. #endif
  188. }
  189. void Start() {
  190. start_ = Env::Default()->NowMicros() * 1e-6;
  191. bytes_ = 0;
  192. message_.clear();
  193. last_op_finish_ = start_;
  194. hist_.Clear();
  195. done_ = 0;
  196. next_report_ = 100;
  197. }
  198. void FinishedSingleOp() {
  199. if (FLAGS_histogram) {
  200. double now = Env::Default()->NowMicros() * 1e-6;
  201. double micros = (now - last_op_finish_) * 1e6;
  202. hist_.Add(micros);
  203. if (micros > 20000) {
  204. fprintf(stderr, "long op: %.1f micros%30s\r", micros, "");
  205. fflush(stderr);
  206. }
  207. last_op_finish_ = now;
  208. }
  209. done_++;
  210. if (done_ >= next_report_) {
  211. if (next_report_ < 1000) next_report_ += 100;
  212. else if (next_report_ < 5000) next_report_ += 500;
  213. else if (next_report_ < 10000) next_report_ += 1000;
  214. else if (next_report_ < 50000) next_report_ += 5000;
  215. else if (next_report_ < 100000) next_report_ += 10000;
  216. else if (next_report_ < 500000) next_report_ += 50000;
  217. else next_report_ += 100000;
  218. fprintf(stderr, "... finished %d ops%30s\r", done_, "");
  219. fflush(stderr);
  220. }
  221. }
  222. void Stop(const Slice& name) {
  223. double finish = Env::Default()->NowMicros() * 1e-6;
  224. // Pretend at least one op was done in case we are running a benchmark
  225. // that does not call FinishedSingleOp().
  226. if (done_ < 1) done_ = 1;
  227. if (bytes_ > 0) {
  228. char rate[100];
  229. snprintf(rate, sizeof(rate), "%6.1f MB/s",
  230. (bytes_ / 1048576.0) / (finish - start_));
  231. if (!message_.empty()) {
  232. message_ = std::string(rate) + " " + message_;
  233. } else {
  234. message_ = rate;
  235. }
  236. }
  237. fprintf(stdout, "%-12s : %11.3f micros/op;%s%s\n",
  238. name.ToString().c_str(),
  239. (finish - start_) * 1e6 / done_,
  240. (message_.empty() ? "" : " "),
  241. message_.c_str());
  242. if (FLAGS_histogram) {
  243. fprintf(stdout, "Microseconds per op:\n%s\n", hist_.ToString().c_str());
  244. }
  245. fflush(stdout);
  246. }
  247. public:
  248. enum Order {
  249. SEQUENTIAL,
  250. RANDOM
  251. };
  252. enum DBState {
  253. FRESH,
  254. EXISTING
  255. };
  256. Benchmark()
  257. : db_(nullptr),
  258. num_(FLAGS_num),
  259. reads_(FLAGS_reads < 0 ? FLAGS_num : FLAGS_reads),
  260. bytes_(0),
  261. rand_(301) {
  262. std::vector<std::string> files;
  263. std::string test_dir;
  264. Env::Default()->GetTestDirectory(&test_dir);
  265. Env::Default()->GetChildren(test_dir.c_str(), &files);
  266. if (!FLAGS_use_existing_db) {
  267. for (int i = 0; i < files.size(); i++) {
  268. if (Slice(files[i]).starts_with("dbbench_polyDB")) {
  269. std::string file_name(test_dir);
  270. file_name += "/";
  271. file_name += files[i];
  272. Env::Default()->DeleteFile(file_name.c_str());
  273. }
  274. }
  275. }
  276. }
  277. ~Benchmark() {
  278. if (!db_->close()) {
  279. fprintf(stderr, "close error: %s\n", db_->error().name());
  280. }
  281. }
  282. void Run() {
  283. PrintHeader();
  284. Open(false);
  285. const char* benchmarks = FLAGS_benchmarks;
  286. while (benchmarks != nullptr) {
  287. const char* sep = strchr(benchmarks, ',');
  288. Slice name;
  289. if (sep == nullptr) {
  290. name = benchmarks;
  291. benchmarks = nullptr;
  292. } else {
  293. name = Slice(benchmarks, sep - benchmarks);
  294. benchmarks = sep + 1;
  295. }
  296. Start();
  297. bool known = true;
  298. bool write_sync = false;
  299. if (name == Slice("fillseq")) {
  300. Write(write_sync, SEQUENTIAL, FRESH, num_, FLAGS_value_size, 1);
  301. DBSynchronize(db_);
  302. } else if (name == Slice("fillrandom")) {
  303. Write(write_sync, RANDOM, FRESH, num_, FLAGS_value_size, 1);
  304. DBSynchronize(db_);
  305. } else if (name == Slice("overwrite")) {
  306. Write(write_sync, RANDOM, EXISTING, num_, FLAGS_value_size, 1);
  307. DBSynchronize(db_);
  308. } else if (name == Slice("fillrandsync")) {
  309. write_sync = true;
  310. Write(write_sync, RANDOM, FRESH, num_ / 100, FLAGS_value_size, 1);
  311. DBSynchronize(db_);
  312. } else if (name == Slice("fillseqsync")) {
  313. write_sync = true;
  314. Write(write_sync, SEQUENTIAL, FRESH, num_ / 100, FLAGS_value_size, 1);
  315. DBSynchronize(db_);
  316. } else if (name == Slice("fillrand100K")) {
  317. Write(write_sync, RANDOM, FRESH, num_ / 1000, 100 * 1000, 1);
  318. DBSynchronize(db_);
  319. } else if (name == Slice("fillseq100K")) {
  320. Write(write_sync, SEQUENTIAL, FRESH, num_ / 1000, 100 * 1000, 1);
  321. DBSynchronize(db_);
  322. } else if (name == Slice("readseq")) {
  323. ReadSequential();
  324. } else if (name == Slice("readrandom")) {
  325. ReadRandom();
  326. } else if (name == Slice("readrand100K")) {
  327. int n = reads_;
  328. reads_ /= 1000;
  329. ReadRandom();
  330. reads_ = n;
  331. } else if (name == Slice("readseq100K")) {
  332. int n = reads_;
  333. reads_ /= 1000;
  334. ReadSequential();
  335. reads_ = n;
  336. } else {
  337. known = false;
  338. if (name != Slice()) { // No error message for empty name
  339. fprintf(stderr, "unknown benchmark '%s'\n", name.ToString().c_str());
  340. }
  341. }
  342. if (known) {
  343. Stop(name);
  344. }
  345. }
  346. }
  347. private:
  348. void Open(bool sync) {
  349. assert(db_ == nullptr);
  350. // Initialize db_
  351. db_ = new kyotocabinet::TreeDB();
  352. char file_name[100];
  353. db_num_++;
  354. std::string test_dir;
  355. Env::Default()->GetTestDirectory(&test_dir);
  356. snprintf(file_name, sizeof(file_name),
  357. "%s/dbbench_polyDB-%d.kct",
  358. test_dir.c_str(),
  359. db_num_);
  360. // Create tuning options and open the database
  361. int open_options = kyotocabinet::PolyDB::OWRITER |
  362. kyotocabinet::PolyDB::OCREATE;
  363. int tune_options = kyotocabinet::TreeDB::TSMALL |
  364. kyotocabinet::TreeDB::TLINEAR;
  365. if (FLAGS_compression) {
  366. tune_options |= kyotocabinet::TreeDB::TCOMPRESS;
  367. db_->tune_compressor(&comp_);
  368. }
  369. db_->tune_options(tune_options);
  370. db_->tune_page_cache(FLAGS_cache_size);
  371. db_->tune_page(FLAGS_page_size);
  372. db_->tune_map(256LL<<20);
  373. if (sync) {
  374. open_options |= kyotocabinet::PolyDB::OAUTOSYNC;
  375. }
  376. if (!db_->open(file_name, open_options)) {
  377. fprintf(stderr, "open error: %s\n", db_->error().name());
  378. }
  379. }
  380. void Write(bool sync, Order order, DBState state,
  381. int num_entries, int value_size, int entries_per_batch) {
  382. // Create new database if state == FRESH
  383. if (state == FRESH) {
  384. if (FLAGS_use_existing_db) {
  385. message_ = "skipping (--use_existing_db is true)";
  386. return;
  387. }
  388. delete db_;
  389. db_ = nullptr;
  390. Open(sync);
  391. Start(); // Do not count time taken to destroy/open
  392. }
  393. if (num_entries != num_) {
  394. char msg[100];
  395. snprintf(msg, sizeof(msg), "(%d ops)", num_entries);
  396. message_ = msg;
  397. }
  398. // Write to database
  399. for (int i = 0; i < num_entries; i++)
  400. {
  401. const int k = (order == SEQUENTIAL) ? i : (rand_.Next() % num_entries);
  402. char key[100];
  403. snprintf(key, sizeof(key), "%016d", k);
  404. bytes_ += value_size + strlen(key);
  405. std::string cpp_key = key;
  406. if (!db_->set(cpp_key, gen_.Generate(value_size).ToString())) {
  407. fprintf(stderr, "set error: %s\n", db_->error().name());
  408. }
  409. FinishedSingleOp();
  410. }
  411. }
  412. void ReadSequential() {
  413. kyotocabinet::DB::Cursor* cur = db_->cursor();
  414. cur->jump();
  415. std::string ckey, cvalue;
  416. while (cur->get(&ckey, &cvalue, true)) {
  417. bytes_ += ckey.size() + cvalue.size();
  418. FinishedSingleOp();
  419. }
  420. delete cur;
  421. }
  422. void ReadRandom() {
  423. std::string value;
  424. for (int i = 0; i < reads_; i++) {
  425. char key[100];
  426. const int k = rand_.Next() % reads_;
  427. snprintf(key, sizeof(key), "%016d", k);
  428. db_->get(key, &value);
  429. FinishedSingleOp();
  430. }
  431. }
  432. };
  433. } // namespace leveldb
  434. int main(int argc, char** argv) {
  435. std::string default_db_path;
  436. for (int i = 1; i < argc; i++) {
  437. double d;
  438. int n;
  439. char junk;
  440. if (leveldb::Slice(argv[i]).starts_with("--benchmarks=")) {
  441. FLAGS_benchmarks = argv[i] + strlen("--benchmarks=");
  442. } else if (sscanf(argv[i], "--compression_ratio=%lf%c", &d, &junk) == 1) {
  443. FLAGS_compression_ratio = d;
  444. } else if (sscanf(argv[i], "--histogram=%d%c", &n, &junk) == 1 &&
  445. (n == 0 || n == 1)) {
  446. FLAGS_histogram = n;
  447. } else if (sscanf(argv[i], "--num=%d%c", &n, &junk) == 1) {
  448. FLAGS_num = n;
  449. } else if (sscanf(argv[i], "--reads=%d%c", &n, &junk) == 1) {
  450. FLAGS_reads = n;
  451. } else if (sscanf(argv[i], "--value_size=%d%c", &n, &junk) == 1) {
  452. FLAGS_value_size = n;
  453. } else if (sscanf(argv[i], "--cache_size=%d%c", &n, &junk) == 1) {
  454. FLAGS_cache_size = n;
  455. } else if (sscanf(argv[i], "--page_size=%d%c", &n, &junk) == 1) {
  456. FLAGS_page_size = n;
  457. } else if (sscanf(argv[i], "--compression=%d%c", &n, &junk) == 1 &&
  458. (n == 0 || n == 1)) {
  459. FLAGS_compression = (n == 1) ? true : false;
  460. } else if (strncmp(argv[i], "--db=", 5) == 0) {
  461. FLAGS_db = argv[i] + 5;
  462. } else {
  463. fprintf(stderr, "Invalid flag '%s'\n", argv[i]);
  464. exit(1);
  465. }
  466. }
  467. // Choose a location for the test database if none given with --db=<path>
  468. if (FLAGS_db == nullptr) {
  469. leveldb::Env::Default()->GetTestDirectory(&default_db_path);
  470. default_db_path += "/dbbench";
  471. FLAGS_db = default_db_path.c_str();
  472. }
  473. leveldb::Benchmark benchmark;
  474. benchmark.Run();
  475. return 0;
  476. }