3#include "../include/cppx.h"
17#include <unordered_set>
23 using Clock = std::chrono::steady_clock;
25 using Duration = std::chrono::duration<double, std::milli>;
29 m_start = Clock::now();
35 m_elapsed = std::chrono::duration_cast<Duration>(m_end - m_start);
40 return m_elapsed.count();
49static std::vector<int> generate_unique_random(std::size_t count,
unsigned seed = 42)
51 std::mt19937 rng(seed);
52 std::unordered_set<int> unique_set;
53 unique_set.reserve(
static_cast<std::size_t
>(count * 1.3));
54 while (unique_set.size() < count)
55 unique_set.insert(
static_cast<int>(rng()));
56 return {unique_set.begin(), unique_set.end()};
59static std::vector<int> pick_random(
const std::vector<int> &source, std::size_t count,
unsigned seed = 123)
61 std::mt19937 rng(seed);
62 std::vector<int> copy = source;
63 std::shuffle(copy.begin(), copy.end(), rng);
64 copy.resize(std::min(count, copy.size()));
77static const int WARMUP_RUNS = 1;
78static const int BENCH_RUNS = 3;
80template <
typename SetupFn,
typename BenchFn>
static double run_timed(SetupFn setup, BenchFn bench,
int runs)
85 std::vector<double> times;
86 times.reserve(
static_cast<std::size_t
>(runs));
87 for (
int i = 0; i < runs; ++i)
95 std::sort(times.begin(), times.end());
96 return times[
static_cast<std::size_t
>(runs / 2)];
99static BenchmarkResult bench_std_set(std::size_t n,
const std::vector<int> &data,
const std::vector<int> &lookup_data,
100 const std::vector<int> &delete_data)
105 for (
int i = 0; i < WARMUP_RUNS; ++i)
112 std::vector<double> ins_times, lkp_times, del_times;
114 for (
int i = 0; i < BENCH_RUNS; ++i)
124 volatile bool sink =
false;
125 for (
int v : lookup_data)
126 sink = (s.find(v) != s.end());
132 for (
int v : delete_data)
138 std::sort(ins_times.begin(), ins_times.end());
139 std::sort(lkp_times.begin(), lkp_times.end());
140 std::sort(del_times.begin(), del_times.end());
142 return {
"std::set", n, ins_times[BENCH_RUNS / 2], lkp_times[BENCH_RUNS / 2], del_times[BENCH_RUNS / 2]};
145static BenchmarkResult bench_std_map(std::size_t n,
const std::vector<int> &data,
const std::vector<int> &lookup_data,
146 const std::vector<int> &delete_data)
149 std::map<int, int> m;
151 for (
int i = 0; i < WARMUP_RUNS; ++i)
158 std::vector<double> ins_times, lkp_times, del_times;
160 for (
int i = 0; i < BENCH_RUNS; ++i)
170 volatile bool sink =
false;
171 for (
int v : lookup_data)
172 sink = (m.find(v) != m.end());
178 for (
int v : delete_data)
184 std::sort(ins_times.begin(), ins_times.end());
185 std::sort(lkp_times.begin(), lkp_times.end());
186 std::sort(del_times.begin(), del_times.end());
188 return {
"std::map", n, ins_times[BENCH_RUNS / 2], lkp_times[BENCH_RUNS / 2], del_times[BENCH_RUNS / 2]};
191static BenchmarkResult bench_std_unordered_set(std::size_t n,
const std::vector<int> &data,
192 const std::vector<int> &lookup_data,
const std::vector<int> &delete_data)
195 std::unordered_set<int> s;
198 for (
int i = 0; i < WARMUP_RUNS; ++i)
206 std::vector<double> ins_times, lkp_times, del_times;
208 for (
int i = 0; i < BENCH_RUNS; ++i)
219 volatile bool sink =
false;
220 for (
int v : lookup_data)
221 sink = (s.find(v) != s.end());
227 for (
int v : delete_data)
233 std::sort(ins_times.begin(), ins_times.end());
234 std::sort(lkp_times.begin(), lkp_times.end());
235 std::sort(del_times.begin(), del_times.end());
237 return {
"std::unordered_set", n, ins_times[BENCH_RUNS / 2], lkp_times[BENCH_RUNS / 2], del_times[BENCH_RUNS / 2]};
240static BenchmarkResult bench_avl(std::size_t n,
const std::vector<int> &data,
const std::vector<int> &lookup_data,
241 const std::vector<int> &delete_data)
244 std::vector<double> ins_times, lkp_times, del_times;
246 for (
int i = 0; i < WARMUP_RUNS; ++i)
253 for (
int i = 0; i < BENCH_RUNS; ++i)
263 volatile bool sink =
false;
264 for (
int v : lookup_data)
265 sink = tree.contains(v);
271 for (
int v : delete_data)
277 std::sort(ins_times.begin(), ins_times.end());
278 std::sort(lkp_times.begin(), lkp_times.end());
279 std::sort(del_times.begin(), del_times.end());
281 return {
"stl_ext::AVLTree", n, ins_times[BENCH_RUNS / 2], lkp_times[BENCH_RUNS / 2], del_times[BENCH_RUNS / 2]};
284static BenchmarkResult bench_rbt(std::size_t n,
const std::vector<int> &data,
const std::vector<int> &lookup_data,
285 const std::vector<int> &delete_data)
288 std::vector<double> ins_times, lkp_times, del_times;
290 for (
int i = 0; i < WARMUP_RUNS; ++i)
297 for (
int i = 0; i < BENCH_RUNS; ++i)
307 volatile bool sink =
false;
308 for (
int v : lookup_data)
309 sink = tree.contains(v);
315 for (
int v : delete_data)
321 std::sort(ins_times.begin(), ins_times.end());
322 std::sort(lkp_times.begin(), lkp_times.end());
323 std::sort(del_times.begin(), del_times.end());
325 return {
"stl_ext::RBTree", n, ins_times[BENCH_RUNS / 2], lkp_times[BENCH_RUNS / 2], del_times[BENCH_RUNS / 2]};
328static BenchmarkResult bench_bst(std::size_t n,
const std::vector<int> &data,
const std::vector<int> &lookup_data,
329 const std::vector<int> &delete_data)
332 std::vector<double> ins_times, lkp_times, del_times;
334 for (
int i = 0; i < WARMUP_RUNS; ++i)
341 for (
int i = 0; i < BENCH_RUNS; ++i)
351 volatile bool sink =
false;
352 for (
int v : lookup_data)
353 sink = tree.contains(v);
359 for (
int v : delete_data)
365 std::sort(ins_times.begin(), ins_times.end());
366 std::sort(lkp_times.begin(), lkp_times.end());
367 std::sort(del_times.begin(), del_times.end());
369 return {
"stl_ext::BST", n, ins_times[BENCH_RUNS / 2], lkp_times[BENCH_RUNS / 2], del_times[BENCH_RUNS / 2]};
372static std::string format_n(std::size_t n)
375 return std::to_string(n / 1'000'000) +
"M";
377 return std::to_string(n / 1'000) +
"K";
378 return std::to_string(n);
381static void print_separator(
int w)
383 std::cout << std::string(static_cast<std::size_t>(w),
'-') <<
"\n";
386static void print_table(
const std::vector<BenchmarkResult> &results)
388 const int w_s = 22, w_n = 8, w_t = 16;
389 const int w_total = w_s + w_n + w_t * 3 + 6;
392 print_separator(w_total);
393 std::cout << std::left << std::setw(w_s) <<
"Structure" << std::right << std::setw(w_n) <<
"N" << std::setw(w_t)
394 <<
"Insert (ms)" << std::setw(w_t) <<
"Lookup (ms)" << std::setw(w_t) <<
"Delete (ms)" <<
"\n";
395 print_separator(w_total);
397 for (
const auto &r : results)
399 std::cout << std::left << std::setw(w_s) << r.structure << std::right << std::setw(w_n) << format_n(r.n)
400 << std::setw(w_t) << std::fixed << std::setprecision(2) << r.insert_ms << std::setw(w_t)
401 << r.lookup_ms << std::setw(w_t) << r.delete_ms <<
"\n";
403 print_separator(w_total);
407static void export_csv(
const std::vector<BenchmarkResult> &results,
const std::string &filename)
409 std::ofstream out(filename);
412 std::cerr <<
"[error] Cannot open " << filename <<
" for writing.\n";
415 out <<
"structure,n,insert_ms,lookup_ms,delete_ms\n";
416 for (
const auto &r : results)
418 out << r.structure <<
"," << r.n <<
"," << std::fixed << std::setprecision(4) << r.insert_ms <<
","
419 << r.lookup_ms <<
"," << r.delete_ms <<
"\n";
421 std::cout <<
"[info] CSV exported to " << filename <<
"\n";
424static std::string svg_color(
const std::string &structure)
426 if (structure ==
"std::set")
428 if (structure ==
"std::map")
430 if (structure ==
"std::unordered_set")
432 if (structure ==
"stl_ext::AVLTree")
434 if (structure ==
"stl_ext::RBTree")
439static std::string svg_escape(
const std::string &s)
465static std::string to_str(
double v,
int prec = 1)
467 std::ostringstream ss;
468 ss << std::fixed << std::setprecision(prec) << v;
472static void emit_subchart(std::ostream &svg,
const std::string &title,
const std::vector<BenchmarkResult> &results,
473 const std::vector<std::string> &structures,
const std::vector<std::size_t> &sizes,
474 double (*value_fn)(
const BenchmarkResult &),
double ox,
double oy,
double chart_w,
479 for (
auto &r : results)
480 max_val = std::max(max_val, value_fn(r));
485 const double margin_left = 60;
486 const double margin_bottom = 40;
487 const double margin_top = 35;
488 const double plot_w = chart_w - margin_left - 10;
489 const double plot_h = chart_h - margin_bottom - margin_top;
491 double px = ox + margin_left;
492 double py = oy + margin_top;
494 svg <<
"<rect x=\"" << ox <<
"\" y=\"" << oy <<
"\" width=\"" << chart_w <<
"\" height=\"" << chart_h
495 <<
"\" rx=\"8\" fill=\"#1e293b\"/>\n";
497 svg <<
"<text x=\"" << ox + chart_w / 2 <<
"\" y=\"" << oy + 22
498 <<
"\" text-anchor=\"middle\" font-size=\"14\" font-weight=\"bold\" fill=\"white\">" << title <<
"</text>\n";
500 for (
int i = 0; i <= 4; ++i)
502 double frac = i / 4.0;
503 double yy = py + plot_h - frac * plot_h;
504 double label_val = frac * max_val;
505 svg <<
"<line x1=\"" << px <<
"\" y1=\"" << yy <<
"\" x2=\"" << px + plot_w <<
"\" y2=\"" << yy
506 <<
"\" stroke=\"#334155\" stroke-width=\"0.5\"/>\n";
507 svg <<
"<text x=\"" << px - 6 <<
"\" y=\"" << yy + 4
508 <<
"\" text-anchor=\"end\" font-size=\"9\" fill=\"#94a3b8\">" << to_str(label_val, (max_val > 100 ? 0 : 1))
512 svg <<
"<text x=\"" << ox + 14 <<
"\" y=\"" << py + plot_h / 2
513 <<
"\" text-anchor=\"middle\" font-size=\"10\" fill=\"#94a3b8\" " <<
"transform=\"rotate(-90," << ox + 14 <<
","
514 << py + plot_h / 2 <<
")\">Time (ms)</text>\n";
516 std::size_t n_groups = sizes.size();
517 std::size_t n_bars = structures.size();
518 double group_w = plot_w /
static_cast<double>(n_groups);
519 double bar_w = group_w / (
static_cast<double>(n_bars) + 1.5);
520 double gap = bar_w * 0.25;
522 for (std::size_t gi = 0; gi < n_groups; ++gi)
524 double gx = px + gi * group_w;
526 svg <<
"<text x=\"" << gx + group_w / 2 <<
"\" y=\"" << py + plot_h + 18
527 <<
"\" text-anchor=\"middle\" font-size=\"11\" fill=\"#94a3b8\">" << format_n(sizes[gi]) <<
"</text>\n";
529 std::size_t bar_idx = 0;
530 for (std::size_t si = 0; si < n_bars; ++si)
534 for (
auto &r : results)
536 if (r.structure == structures[si] && r.n == sizes[gi])
545 double val = value_fn(*match);
546 double bh = (val / max_val) * plot_h;
547 double bx = gx + gap + bar_idx * (bar_w + gap);
548 double by = py + plot_h - bh;
550 svg <<
"<rect x=\"" << bx <<
"\" y=\"" << by <<
"\" width=\"" << bar_w <<
"\" height=\"" << bh
551 <<
"\" rx=\"2\" fill=\"" << svg_color(structures[si]) <<
"\" opacity=\"0.9\"/>\n";
553 svg <<
"<text x=\"" << bx + bar_w / 2 <<
"\" y=\"" << by - 3
554 <<
"\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"bold\" fill=\"white\">" << to_str(val)
562static void generate_svg(
const std::vector<BenchmarkResult> &results,
const std::string &filename)
565 std::vector<std::string> structures;
566 std::vector<std::size_t> sizes;
567 for (
auto &r : results)
569 if (std::find(structures.begin(), structures.end(), r.structure) == structures.end())
570 structures.push_back(r.structure);
571 if (std::find(sizes.begin(), sizes.end(), r.n) == sizes.end())
572 sizes.push_back(r.n);
574 std::sort(sizes.begin(), sizes.end());
576 const double chart_w = 380;
577 const double chart_h = 300;
578 const double pad = 20;
579 const double legend_h = 60;
580 const double title_h = 40;
581 const double total_w = 3 * chart_w + 4 * pad;
582 const double total_h = title_h + chart_h + legend_h + 2 * pad;
584 std::ofstream f(filename);
587 std::cerr <<
"[error] Cannot write " << filename <<
"\n";
591 f <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
592 f <<
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 " << total_w <<
" " << total_h <<
"\">\n";
593 f <<
"<style>text { font-family: 'Segoe UI', Roboto, Arial, sans-serif; }</style>\n";
595 f <<
"<rect width=\"100%\" height=\"100%\" rx=\"12\" fill=\"#0f172a\"/>\n";
597 f <<
"<text x=\"" << total_w / 2 <<
"\" y=\"28\" text-anchor=\"middle\" font-size=\"20\" "
598 <<
"font-weight=\"bold\" fill=\"white\">CPPX Benchmark Results</text>\n";
600 auto insert_fn = [](
const BenchmarkResult &r) ->
double {
return r.insert_ms; };
601 auto lookup_fn = [](
const BenchmarkResult &r) ->
double {
return r.lookup_ms; };
602 auto delete_fn = [](
const BenchmarkResult &r) ->
double {
return r.delete_ms; };
604 double y_charts = title_h;
605 emit_subchart(f,
"Insert Time", results, structures, sizes, insert_fn, pad, y_charts, chart_w, chart_h);
606 emit_subchart(f,
"Lookup Time", results, structures, sizes, lookup_fn, pad + chart_w + pad, y_charts, chart_w,
608 emit_subchart(f,
"Delete Time", results, structures, sizes, delete_fn, pad + 2 * (chart_w + pad), y_charts, chart_w,
611 double ly = y_charts + chart_h + 18;
613 double lx_start = total_w / 2 - (structures.size() * item_w) / 2.0;
614 for (std::size_t i = 0; i < structures.size(); ++i)
616 double lx = lx_start + i * item_w;
617 f <<
"<rect x=\"" << lx <<
"\" y=\"" << ly <<
"\" width=\"14\" height=\"14\" rx=\"3\" fill=\""
618 << svg_color(structures[i]) <<
"\"/>\n";
619 f <<
"<text x=\"" << lx + 20 <<
"\" y=\"" << ly + 12 <<
"\" font-size=\"12\" fill=\"white\">"
620 << svg_escape(structures[i]) <<
"</text>\n";
624 std::cout <<
"[info] SVG chart saved to " << filename <<
"\n";
627int main(
int argc,
char *argv[])
629 std::string out_dir =
".";
633 const std::vector<std::size_t> sizes = {10'000, 100'000, 1'000'000};
635 std::vector<BenchmarkResult> all_results;
636 all_results.reserve(sizes.size() * 6);
638 std::cout <<
"╔══════════════════════════════════════════════════════════════╗\n";
639 std::cout <<
"║ CPPX Benchmark Suite v3.0 ║\n";
640 std::cout <<
"║ Arena allocator · Iterative AVL · Raw pointers ║\n";
641 std::cout <<
"╚══════════════════════════════════════════════════════════════╝\n";
643 for (std::size_t n : sizes)
645 std::cout <<
"\n▶ Benchmarking N = " << format_n(n) <<
" (" << n <<
" elements) ...\n";
647 auto data = generate_unique_random(n);
648 auto lookup_data = pick_random(data, n / 2);
649 auto delete_data = pick_random(data, n / 10);
651 std::mt19937 rng(99);
652 std::shuffle(data.begin(), data.end(), rng);
654 std::cout <<
" ├─ std::set ... " << std::flush;
655 auto r1 = bench_std_set(n, data, lookup_data, delete_data);
656 std::cout <<
"done\n";
658 std::cout <<
" ├─ std::map ... " << std::flush;
659 auto r_map = bench_std_map(n, data, lookup_data, delete_data);
660 std::cout <<
"done\n";
662 std::cout <<
" ├─ std::unordered_set ... " << std::flush;
663 auto r_uset = bench_std_unordered_set(n, data, lookup_data, delete_data);
664 std::cout <<
"done\n";
666 std::cout <<
" ├─ stl_ext::AVLTree ... " << std::flush;
667 auto r2 = bench_avl(n, data, lookup_data, delete_data);
668 std::cout <<
"done\n";
670 std::cout <<
" ├─ stl_ext::RBTree ... " << std::flush;
671 auto r_rbt = bench_rbt(n, data, lookup_data, delete_data);
672 std::cout <<
"done\n";
676 std::cout <<
" └─ stl_ext::BST ... " << std::flush;
677 auto r3 = bench_bst(n, data, lookup_data, delete_data);
678 std::cout <<
"done\n";
679 all_results.push_back(r3);
683 std::cout <<
" └─ stl_ext::BST ... skipped (N too large for unbalanced tree)\n";
686 all_results.push_back(r1);
687 all_results.push_back(r_map);
688 all_results.push_back(r_uset);
689 all_results.push_back(r2);
690 all_results.push_back(r_rbt);
696 return a.structure < b.structure;
699 print_table(all_results);
700 export_csv(all_results, out_dir +
"/docs/benchmark_results.csv");
701 generate_svg(all_results, out_dir +
"/docs/benchmark_chart.svg");
int main(int argc, char *argv[])
std::chrono::duration< double, std::milli > Duration
double elapsed_ms() const
Clock::time_point TimePoint
std::chrono::steady_clock Clock