rcx

library of miscellaneous bits of C code
git clone git://git.rr3.xyz/rcx
Log | Files | Refs | README | LICENSE

commit 2064b054b1037824f2ad243f9dd86ae6c03f8a14
parent dd4e201390262f46d2b91b9815706a82561eb3d9
Author: robert <robertrussell.72001@gmail.com>
Date:   Wed, 10 Aug 2022 12:05:06 -0700

Add benchmarking module

Diffstat:
MMakefile | 4+++-
Minc/cext/all.h | 1+
Ainc/cext/bench.h | 28++++++++++++++++++++++++++++
Asrc/bench.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 131 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile @@ -4,6 +4,7 @@ include config.mk SRC =\ src/alloc.c\ + src/bench.c\ src/log.c\ src/opt.c\ src/str.c\ @@ -16,9 +17,10 @@ libcext.a: $(SRC:.c=.o) $(CC) -c -o $@ $(CFLAGS) $< src/alloc.o: src/alloc.c inc/cext/cext.h inc/cext/def.h inc/cext/alloc.h inc/cext/log.h config.mk +src/bench.o: src/bench.c inc/cext/cext.h inc/cext/def.h inc/cext/bench.h inc/cext/log.h config.mk src/log.o: src/log.c inc/cext/cext.h inc/cext/def.h inc/cext/log.h config.mk src/opt.o: src/opt.c inc/cext/cext.h inc/cext/def.h inc/cext/opt.h config.mk -src/str.o: src/str.c inc/cext/cext.h inc/cext/def.h inc/cext/str.h config.mk +src/str.o: src/str.c inc/cext/cext.h inc/cext/def.h inc/cext/str.h config.mk # TODO missing deps src/utf8.o: src/utf8.c inc/cext/cext.h inc/cext/def.h inc/cext/utf8.h config.mk clean: diff --git a/inc/cext/all.h b/inc/cext/all.h @@ -1,3 +1,4 @@ +/* Everything except bench.h */ #include "cext/alloc.h" #include "cext/cext.h" #include "cext/deque.h" diff --git a/inc/cext/bench.h b/inc/cext/bench.h @@ -0,0 +1,28 @@ +#pragma once + +#include "cext/def.h" + +/* +Usage: + void my_benchmark(u64 N) { + <initialization (not timed)> + bench_start(); + for (u64 i = 0; i < N; i++) { + <code to benchmark> + } + bench_stop(); + <cleanup (not timed)> + } + int main(void) { + bench("my benchmark", my_benchmark, 3*SECONDS); + } +Note that <code to benchmark> can contain calls to bench_stop and bench_start +to pause and restart timing. +*/ + +#define MILLISECONDS 1000000ULL +#define SECONDS 1000000000ULL + +void bench(char *name, void (*fn)(u64 N), u64 goalns); +void bench_start(void); +void bench_stop(void); diff --git a/src/bench.c b/src/bench.c @@ -0,0 +1,99 @@ +#define _POSIX_C_SOURCE 199309L /* clock_gettime */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "cext/bench.h" +#include "cext/cext.h" +#include "cext/log.h" + +#ifndef _POSIX_THREAD_CPUTIME +#error "Need CLOCK_PROCESS_CPUTIME_ID" +#endif + +#define MAXN 1000000000ULL + +static u64 run(void (*fn)(u64 N), u64 N); +static u64 requiredN(u64 prevN, u64 prevns, u64 goalns); +static void printnsop(u64 N, u64 ns); + +static bool started; /* Has bench_start been called? */ +static bool active; /* Is the timer currently on? */ +static struct timespec start; +static u64 accumns; + +u64 +run(void (*fn)(u64 N), u64 N) { + started = active = false; + accumns = 0; + fn(N); + if (active || !started) + fatalf("bench misuse"); + return accumns; +} + +u64 +requiredN(u64 prevN, u64 prevns, u64 goalns) { + /* This is pretty much copied from Go's testing package. */ + u64 N = prevN * goalns / (prevns == 0 ? 1 : prevns); + N += N/5; /* Overestimate by 1.2x. */ + N = MIN(N, 100*prevN); /* Grow slowly, in case prevns is inaccurate. */ + N = MAX(N, prevN+1); /* Do at least one more run, */ + N = MIN(N, MAXN); /* but don't do too many. */ + return N; +} + +void +printnsop(u64 N, u64 ns) { + double nsop = (double) ns / N; + + /* This is pretty much copied from Go's testing package. */ + char *format; + if (nsop == 0 || nsop >= 99.95) format = "%10.0f ns/op"; + else if (nsop >= 9.995) format = "%12.1f ns/op"; + else if (nsop >= 0.9995) format = "%13.2f ns/op"; + else if (nsop >= 0.09995) format = "%14.3f ns/op"; + else if (nsop >= 0.009995) format = "%15.4f ns/op"; + else if (nsop >= 0.0009995) format = "%16.5f ns/op"; + else format = "%17.6f ns/op"; + + fprintf(stderr, format, nsop); +} + +void +bench(char *name, void (*fn)(u64 N), u64 goalns) { + run(fn, 1); /* Warmup */ + u64 N = 1; + u64 ns; + while ((ns = run(fn, N)) < goalns && N < MAXN) + N = requiredN(N, ns, goalns); + + fprintf(stderr, "benchmark: %-25s%10"PRId64" iters ", name, N); + printnsop(N, ns); + fprintf(stderr, "\n"); +} + +void +bench_start(void) { + if (active) + return; + active = true; + started = true; + if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start) < 0) + fatalf("clock_gettime: %s", strerror(errno)); +} + +void +bench_stop(void) { + if (!active) + return; + struct timespec stop; + if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &stop) < 0) + fatalf("clock_gettime: %s", strerror(errno)); + active = false; + accumns += (stop.tv_sec - start.tv_sec) * 1000000000ULL + + (stop.tv_nsec - start.tv_nsec); +}