From f72d334804dbd811b64424b3cc92be5df5784ce4 Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 25 Dec 2022 15:40:09 +0100 Subject: [PATCH] Ready. Set. Go! --- .gitignore | 1 + Makefile | 8 +++ cbench.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 cbench.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c8b673 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cbench \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..af5b90a --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +cbench: cbench.c + $(CC) -O2 -o cbench cbench.c -DNDEBUG + +DESTDIR := $(if $(DESTDIR),$(DESTDIR),/usr/local) + +install: + mkdir -p $(DESTDIR)/bin + cp ./cbench $(DESTDIR)/bin/ \ No newline at end of file diff --git a/cbench.c b/cbench.c new file mode 100644 index 0000000..a35e253 --- /dev/null +++ b/cbench.c @@ -0,0 +1,142 @@ +#define _POSIX_C_SOURCE 199309L +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline uint64_t as_nanoseconds(struct timespec* ts) { + return ts->tv_sec * (uint64_t)1000000000L + ts->tv_nsec; +} + +static void as_timespec(uint64_t ns, struct timespec* ts) { + ts->tv_sec = ns / (uint64_t)1000000000L; + ts->tv_nsec = ns % (uint64_t)1000000000L; +} + +uint64_t run_individual_benchmark(char** argv) +{ + struct timespec start, end; + + int fd = open("/dev/null", O_WRONLY); + if(fd < 0) + { + perror("open"); + return (uint64_t)-1; + } + + if(clock_gettime(CLOCK_MONOTONIC, &start) != 0) + { + perror("clock_gettime"); + return (uint64_t)-1; + } + + pid_t child = fork(); + if(child < 0) + { + perror("fork"); + return (uint64_t)-1; + } + + if(child == 0) + { + dup2(fd, STDOUT_FILENO); + execvp(argv[0], argv); + perror("execvp"); + exit(1); + } + + close(fd); + int status; + pid_t result = waitpid(child, &status, 0); + + // We fetch the time before checking waitpid's status because we want to have a somewhat accurate result. + if(clock_gettime(CLOCK_MONOTONIC, &end) != 0) + { + perror("clock_gettime"); + return (uint64_t)-1; + } + + if(result != child) + { + perror("waitpid"); + return (uint64_t)-1; + } + + if(WIFEXITED(status) && WEXITSTATUS(status) != 0) + { + fprintf(stderr, "Child exited with status %d\n", WEXITSTATUS(status)); + return (uint64_t)-1; + } + + return as_nanoseconds(&end) - as_nanoseconds(&start); +} + +int main(int argc, char** argv) +{ + if(argc == 1) + { + fprintf(stderr, "Usage: %s [iterations:20] command\n", argv[0]); + return 1; + } + + if(!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) + { + printf("Usage: %s [iterations:20] command\n" + "\n" + "Benchmarks a program by running it times and reporting the \n" + "average, fastest and slowest time it took to run.\n", argv[0]); + return 0; + } + + int iterations = 20; + + if(isdigit(*argv[1])) + { + if(argc == 2) + { + fprintf(stderr, "Usage: %s [iterations:20] command\n", argv[0]); + return 1; + } + + iterations = atoi(argv[1]); + + argv++; + } + + uint64_t total = 0; + uint64_t min = UINT64_MAX; + uint64_t max = 0; + + for(int i = 0; i < iterations; i++) + { + uint64_t ns = run_individual_benchmark(argv+1); + if(ns == (uint64_t)-1) + { + return 1; + } + + total += ns; + if(ns < min) min = ns; + if(ns > max) max = ns; + } + + uint64_t average = total / (uint64_t)iterations; + + struct timespec tspec; + as_timespec(total, &tspec); + + printf("Results (over %d iterations): \n", iterations); + + printf("Total time: %3ld.%.6lds\n", tspec.tv_sec, tspec.tv_nsec / 1000L); + as_timespec(average, &tspec); + printf("Average time: %3ld.%.6lds\n", tspec.tv_sec, tspec.tv_nsec / 1000L); + as_timespec(min, &tspec); + printf("Fastest time: %3ld.%.6lds\n", tspec.tv_sec, tspec.tv_nsec / 1000L); + as_timespec(max, &tspec); + printf("Slowest time: %3ld.%.6lds\n", tspec.tv_sec, tspec.tv_nsec / 1000L); +} \ No newline at end of file