From e1b5725e74b2ce40d424647d9c98d35e1a536aca Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sun, 10 Nov 2024 13:16:10 +0000 Subject: [PATCH] Create benchmarking library --- CMakeLists.txt | 1 + benchmarks/CMakeLists.txt | 16 ++++++ benchmarks/benchmarking.c | 85 +++++++++++++++++++++++++++++++ benchmarks/include/benchmarking.h | 50 ++++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 benchmarks/CMakeLists.txt create mode 100644 benchmarks/benchmarking.c create mode 100644 benchmarks/include/benchmarking.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cb6639..331fb22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,3 +19,4 @@ endfunction() add_subdirectory(lib) add_subdirectory(tests) add_subdirectory(demo) +add_subdirectory(benchmarks) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 0000000..ea18878 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(benchmarking benchmarking.c) +set_default_target_options(benchmarking) +target_include_directories(benchmarking PUBLIC include) + +function(add_benchmark_suite source) + string(REGEX REPLACE ".c$" "" name ${source}) + add_executable(${name} ${source}) + set_default_target_options(${name}) + target_link_libraries(${name} PRIVATE lib benchmarking) +endfunction() + +function(add_benchmark_suites) + foreach(source ${ARGN}) + add_benchmark_suite(${source}) + endforeach() +endfunction() diff --git a/benchmarks/benchmarking.c b/benchmarks/benchmarking.c new file mode 100644 index 0000000..a843083 --- /dev/null +++ b/benchmarks/benchmarking.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) Camden Dixie O'Brien + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#include "benchmarking.h" + +#include +#include +#include + +#define SWAP(x, y) \ + do { \ + const double tmp = x; \ + x = y; \ + y = tmp; \ + } while (0) + +clock_t benchmark_start, benchmark_end; + +static void sort(double *xs, int n) +{ + if (n <= 0) + return; + + const double pivot = xs[(n - 1) / 2]; + int lt = 0; + int eq = 0; + int gt = n - 1; + while (eq <= gt) { + if (xs[eq] < pivot) { + SWAP(xs[eq], xs[lt]); + ++lt; + ++eq; + } else if (xs[eq] > pivot) { + SWAP(xs[eq], xs[gt]); + --gt; + } else { + ++eq; + } + } + + sort(xs, lt); + sort(xs + gt + 1, n - (gt + 1)); +} + +void benchmark_summarise(double *res, int reps, benchmark_summary_t *out) +{ + assert(reps > 0); + + sort(res, reps); + const double median = res[reps / 2]; + + double sum = 0; + for (int i = 0; i < reps; ++i) + sum += res[i]; + const double mean = sum / reps; + + double diff_sum = 0; + for (int i = 0; i < reps; ++i) + diff_sum += pow(res[i] - mean, 2); + const double variance = diff_sum / (reps - 1); + + out->reps = reps; + out->total = sum; + out->median = median; + out->mean = mean; + out->min = res[0]; + out->max = res[reps - 1]; + out->stddev = sqrt(variance); +} + +void benchmark_print_header(void) +{ + printf( + "%-12s %13s %13s %13s %13s %12s\n", "benchmark", "median (µs)", + "mean (µs)", "min (µs)", "max (µs)", "stddev"); +} + +void benchmark_print(const char *name, const benchmark_summary_t *s) +{ + printf( + "%-12s %12.2f %12.2f %12.2f %12.2f %12.2f\n", name, s->median, + s->mean, s->min, s->max, s->stddev); +} diff --git a/benchmarks/include/benchmarking.h b/benchmarks/include/benchmarking.h new file mode 100644 index 0000000..3dd07c4 --- /dev/null +++ b/benchmarks/include/benchmarking.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) Camden Dixie O'Brien + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#ifndef BENCHMARKING_H +#define BENCHMARKING_H + +#include + +typedef struct { + int reps; + double total, median, mean, min, max, stddev; +} benchmark_summary_t; + +#define CLOCK_MICROS(c) (1000000 * (double)c / CLOCKS_PER_SEC) + +#define BENCHMARKING_BEGIN() benchmark_print_header() +#define BENCHMARKING_END() 0 + +#define START_CLOCK() \ + do { \ + benchmark_start = clock(); \ + } while (0) + +#define STOP_CLOCK() \ + do { \ + benchmark_end = clock(); \ + } while (0) + +#define RUN_BENCHMARK(reps, name, fn, ...) \ + do { \ + double res[reps]; \ + for (int i = 0; i < reps; ++i) { \ + fn(__VA_ARGS__); \ + res[i] = CLOCK_MICROS(benchmark_end) \ + - CLOCK_MICROS(benchmark_start); \ + } \ + benchmark_summary_t summary; \ + benchmark_summarise(res, reps, &summary); \ + benchmark_print(name, &summary); \ + } while (0) + +extern clock_t benchmark_start, benchmark_end; + +void benchmark_summarise(double *res, int reps, benchmark_summary_t *out); +void benchmark_print_header(void); +void benchmark_print(const char *name, const benchmark_summary_t *summary); + +#endif