|
|
@@ -0,0 +1,141 @@
|
|
|
+// RSS micro-benchmark — Phase B v1.11 binary doc storage.
|
|
|
+//
|
|
|
+// Loads N synthetic Zoe-shape JSON docs into ONE storage container (chosen by
|
|
|
+// --mode) and reports peak RSS. Run twice and diff: one with --mode=nlohmann
|
|
|
+// (v1.10 baseline), one with --mode=binary (v1.11). Comparing peak RSS across
|
|
|
+// two separate processes avoids the allocator-carry-over confound where the
|
|
|
+// second container in a single process reuses pages freed by the first.
|
|
|
+//
|
|
|
+// Not a regression gate — informational, run by hand to capture the
|
|
|
+// memory delta. Usage:
|
|
|
+// ./bench_doc_memory --mode=nlohmann [N]
|
|
|
+// ./bench_doc_memory --mode=binary [N]
|
|
|
+// ./bench_doc_memory --help
|
|
|
+
|
|
|
+#include <chrono>
|
|
|
+#include <cstdlib>
|
|
|
+#include <cstring>
|
|
|
+#include <fstream>
|
|
|
+#include <iostream>
|
|
|
+#include <random>
|
|
|
+#include <sstream>
|
|
|
+#include <string>
|
|
|
+#include <vector>
|
|
|
+
|
|
|
+#include <nlohmann/json.hpp>
|
|
|
+
|
|
|
+#include "doc_binary.hpp"
|
|
|
+
|
|
|
+namespace bin = smartbotic::db::doc_binary;
|
|
|
+
|
|
|
+namespace {
|
|
|
+
|
|
|
+long rss_kb() {
|
|
|
+ std::ifstream f("/proc/self/status");
|
|
|
+ std::string line;
|
|
|
+ while (std::getline(f, line)) {
|
|
|
+ if (line.rfind("VmRSS:", 0) == 0) {
|
|
|
+ std::istringstream iss(line.substr(6));
|
|
|
+ long kb;
|
|
|
+ iss >> kb;
|
|
|
+ return kb;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+nlohmann::json synth_doc(size_t i, std::mt19937& rng) {
|
|
|
+ return nlohmann::json{
|
|
|
+ {"_id", "doc-" + std::to_string(i)},
|
|
|
+ {"_created_at", static_cast<long long>(1731628800000LL + static_cast<long long>(i))},
|
|
|
+ {"_updated_at", static_cast<long long>(1731628800000LL + static_cast<long long>(i))},
|
|
|
+ {"name", "name-" + std::to_string(i)},
|
|
|
+ {"tags", nlohmann::json::array({"hot", "pinned", "rec"})},
|
|
|
+ {"_vector", nlohmann::json::array({
|
|
|
+ std::uniform_real_distribution<double>(-1.0, 1.0)(rng),
|
|
|
+ std::uniform_real_distribution<double>(-1.0, 1.0)(rng),
|
|
|
+ std::uniform_real_distribution<double>(-1.0, 1.0)(rng),
|
|
|
+ std::uniform_real_distribution<double>(-1.0, 1.0)(rng),
|
|
|
+ std::uniform_real_distribution<double>(-1.0, 1.0)(rng),
|
|
|
+ std::uniform_real_distribution<double>(-1.0, 1.0)(rng),
|
|
|
+ std::uniform_real_distribution<double>(-1.0, 1.0)(rng),
|
|
|
+ std::uniform_real_distribution<double>(-1.0, 1.0)(rng),
|
|
|
+ })},
|
|
|
+ {"meta", {
|
|
|
+ {"flag", (i % 2) == 0},
|
|
|
+ {"score", std::uniform_real_distribution<double>(0.0, 1.0)(rng)},
|
|
|
+ {"counter", static_cast<int>(i % 1000)},
|
|
|
+ }},
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+void usage(const char* prog) {
|
|
|
+ std::cerr << "usage: " << prog << " --mode=<nlohmann|binary> [N]\n"
|
|
|
+ << " N defaults to 100000\n";
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace
|
|
|
+
|
|
|
+int main(int argc, char** argv) {
|
|
|
+ std::string mode;
|
|
|
+ size_t n = 100000;
|
|
|
+ for (int i = 1; i < argc; ++i) {
|
|
|
+ const char* a = argv[i];
|
|
|
+ if (std::strncmp(a, "--mode=", 7) == 0) {
|
|
|
+ mode = a + 7;
|
|
|
+ } else if (std::strcmp(a, "--help") == 0) {
|
|
|
+ usage(argv[0]);
|
|
|
+ return 0;
|
|
|
+ } else {
|
|
|
+ n = static_cast<size_t>(std::atol(a));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (mode != "nlohmann" && mode != "binary") {
|
|
|
+ usage(argv[0]);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ long rss_start = rss_kb();
|
|
|
+ std::cout << "mode: " << mode << "\n"
|
|
|
+ << "docs: " << n << "\n"
|
|
|
+ << "RSS start: " << rss_start << " kB\n";
|
|
|
+
|
|
|
+ auto t0 = std::chrono::steady_clock::now();
|
|
|
+
|
|
|
+ if (mode == "nlohmann") {
|
|
|
+ std::vector<nlohmann::json> store;
|
|
|
+ store.reserve(n);
|
|
|
+ std::mt19937 rng(42);
|
|
|
+ for (size_t i = 0; i < n; ++i) {
|
|
|
+ store.push_back(synth_doc(i, rng));
|
|
|
+ }
|
|
|
+ auto t1 = std::chrono::steady_clock::now();
|
|
|
+ long rss_loaded = rss_kb();
|
|
|
+ std::cout << "load time: "
|
|
|
+ << std::chrono::duration<double, std::milli>(t1 - t0).count() << " ms\n"
|
|
|
+ << "RSS loaded: " << rss_loaded << " kB\n"
|
|
|
+ << "delta: " << (rss_loaded - rss_start) << " kB\n"
|
|
|
+ << "per-doc: "
|
|
|
+ << static_cast<double>(rss_loaded - rss_start) * 1024.0 / static_cast<double>(n)
|
|
|
+ << " bytes\n"
|
|
|
+ << "store size: " << store.size() << "\n";
|
|
|
+ } else {
|
|
|
+ std::vector<bin::Doc> store;
|
|
|
+ store.reserve(n);
|
|
|
+ std::mt19937 rng(42);
|
|
|
+ for (size_t i = 0; i < n; ++i) {
|
|
|
+ store.push_back(bin::encode(synth_doc(i, rng)));
|
|
|
+ }
|
|
|
+ auto t1 = std::chrono::steady_clock::now();
|
|
|
+ long rss_loaded = rss_kb();
|
|
|
+ std::cout << "load time: "
|
|
|
+ << std::chrono::duration<double, std::milli>(t1 - t0).count() << " ms\n"
|
|
|
+ << "RSS loaded: " << rss_loaded << " kB\n"
|
|
|
+ << "delta: " << (rss_loaded - rss_start) << " kB\n"
|
|
|
+ << "per-doc: "
|
|
|
+ << static_cast<double>(rss_loaded - rss_start) * 1024.0 / static_cast<double>(n)
|
|
|
+ << " bytes\n"
|
|
|
+ << "store size: " << store.size() << "\n";
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|