diff options
author | Tavian Barnes <tavianator@tavianator.com> | 2024-11-27 20:19:32 -0500 |
---|---|---|
committer | Tavian Barnes <tavianator@tavianator.com> | 2024-12-03 14:42:05 -0500 |
commit | 3678c2ee7c11d67f4ea97c85d8564cd386a32bd1 (patch) | |
tree | 14303e34fbf1bb6ada99758a3c19b59697a199a5 | |
parent | 35c7c72601cee9692846de13117411d80b3d13f6 (diff) | |
download | bfs-3678c2ee7c11d67f4ea97c85d8564cd386a32bd1.tar.xz |
bench/ioq: New ioq microbenchmark
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | bench/ioq.c | 322 |
2 files changed, 332 insertions, 1 deletions
@@ -45,7 +45,8 @@ BINS := \ bin/tests/mksock \ bin/tests/units \ bin/tests/xspawnee \ - bin/tests/xtouch + bin/tests/xtouch \ + bin/bench/ioq all: ${BINS} .PHONY: all @@ -215,6 +216,14 @@ ${DISTCHECKS}:: && ${MAKE} check TEST_FLAGS="--sudo --verbose=skipped" @test "$${GITHUB_ACTIONS-}" != true || printf '::endgroup::\n' +## Benchmarks (`make bench`) + +bench: bin/bench/ioq +.PHONY: bench + +bin/bench/ioq: obj/bench/ioq.o ${LIBBFS} +OBJS += obj/bench/ioq.o + ## Automatic dependency tracking # Rebuild when the configuration changes diff --git a/bench/ioq.c b/bench/ioq.c new file mode 100644 index 0000000..0886e9e --- /dev/null +++ b/bench/ioq.c @@ -0,0 +1,322 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include "atomic.h" +#include "bfs.h" +#include "bfstd.h" +#include "diag.h" +#include "ioq.h" +#include "sighook.h" +#include "xtime.h" + +#include <errno.h> +#include <locale.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +/** Which clock to use for benchmarking. */ +static clockid_t clockid = CLOCK_REALTIME; + +/** Get a current time measurement. */ +static void gettime(struct timespec *tp) { + int ret = clock_gettime(clockid, tp); + bfs_everify(ret == 0, "clock_gettime(%d)", (int)clockid); +} + +/** + * Time measurements. + */ +struct times { + /** The start time. */ + struct timespec start; + + /** Total requests started. */ + size_t pushed; + /** Total requests finished. */ + size_t popped; + + /** Number of timed requests (latency). */ + size_t timed_reqs; + /** The start time for the currently tracked request. */ + struct timespec req_start; + /** Whether a timed request is currently in flight. */ + bool timing; + + /** Latency measurements. */ + struct { + struct timespec min; + struct timespec max; + struct timespec sum; + } latency; +}; + +/** Initialize a timer. */ +static void times_init(struct times *times) { + *times = (struct times) { + .latency = { + .min = { .tv_sec = 1000 }, + }, + }; + gettime(×->start); +} + +/** Start timing a single request. */ +static void start_request(struct times *times) { + gettime(×->req_start); + times->timing = true; +} + +/** Finish timing a request. */ +static void finish_request(struct times *times) { + struct timespec elapsed; + gettime(&elapsed); + timespec_sub(&elapsed, ×->req_start); + + timespec_min(×->latency.min, &elapsed); + timespec_max(×->latency.max, &elapsed); + timespec_add(×->latency.sum, &elapsed); + + bfs_assert(times->timing); + times->timing = false; + ++times->timed_reqs; +} + +/** Add times to the totals, and reset the lap times. */ +static void times_lap(struct times *total, struct times *lap) { + total->pushed += lap->pushed; + total->popped += lap->popped; + total->timed_reqs += lap->timed_reqs; + + timespec_min(&total->latency.min, &lap->latency.min); + timespec_max(&total->latency.max, &lap->latency.max); + timespec_add(&total->latency.sum, &lap->latency.sum); + + times_init(lap); +} + +/** Print some times. */ +static void times_print(const struct times *times, long seconds) { + struct timespec elapsed; + gettime(&elapsed); + timespec_sub(&elapsed, ×->start); + + double fsec = timespec_ns(&elapsed) / 1.0e9; + double iops = times->popped / fsec; + double mean = timespec_ns(×->latency.sum) / times->timed_reqs; + double min = timespec_ns(×->latency.min); + double max = timespec_ns(×->latency.max); + + if (seconds > 0) { + printf("%9ld", seconds); + } else if (elapsed.tv_nsec >= 10 * 1000 * 1000) { + printf("%9.2f", fsec); + } else { + printf("%9.0f", fsec); + } + + printf(" │ %'17.0f │ %'15.0f ∈ [%'6.0f .. %'7.0f]\n", iops, mean, min, max); + fflush(stdout); +} + +/** Push an ioq request. */ +static bool push(struct ioq *ioq, enum ioq_nop_type type, struct times *lap) { + void *ptr = NULL; + + // Track latency for a small fraction of requests + if (!lap->timing && (lap->pushed + 1) % 128 == 0) { + start_request(lap); + ptr = lap; + } + + int ret = ioq_nop(ioq, type, ptr); + if (ret != 0) { + bfs_everify(errno == EAGAIN, "ioq_nop(%d)", (int)type); + return false; + } + + ++lap->pushed; + return true; +} + +/** Pop an ioq request. */ +static bool pop(struct ioq *ioq, struct times *lap, bool block) { + struct ioq_ent *ent = ioq_pop(ioq, block); + if (!ent) { + return false; + } + + if (ent->ptr) { + finish_request(lap); + } + + ioq_free(ioq, ent); + ++lap->popped; + return true; +} + +/** ^C flag. */ +static atomic bool quit = false; + +/** ^C hook. */ +static void ctrlc(int sig, siginfo_t *info, void *arg) { + store(&quit, true, relaxed); +} + +int main(int argc, char *argv[]) { + // Use CLOCK_MONOTONIC if available +#if defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 + if (sysoption(MONOTONIC_CLOCK) > 0) { + clockid = CLOCK_MONOTONIC; + } +#endif + + // Enable thousands separators + setlocale(LC_ALL, ""); + + // -d: queue depth + long depth = 4096; + // -j: threads + long threads = 0; + // -t: timeout + double timeout = 5.0; + // -L|-H: ioq_nop() type + enum ioq_nop_type type = IOQ_NOP_LIGHT; + + const char *cmd = argc > 0 ? argv[0] : "ioq"; + int c; + while (c = getopt(argc, argv, ":d:j:t:LH"), c != -1) { + switch (c) { + case 'd': + if (xstrtol(optarg, NULL, 10, &depth) != 0) { + fprintf(stderr, "%s: Bad depth '%s': %s\n", cmd, optarg, errstr()); + return EXIT_FAILURE; + } + break; + case 'j': + if (xstrtol(optarg, NULL, 10, &threads) != 0) { + fprintf(stderr, "%s: Bad thread count '%s': %s\n", cmd, optarg, errstr()); + return EXIT_FAILURE; + } + break; + case 't': + if (xstrtod(optarg, NULL, &timeout) != 0) { + fprintf(stderr, "%s: Bad timeout '%s': %s\n", cmd, optarg, errstr()); + return EXIT_FAILURE; + } + break; + case 'L': + type = IOQ_NOP_LIGHT; + break; + case 'H': + type = IOQ_NOP_HEAVY; + break; + case ':': + fprintf(stderr, "%s: Missing argument to -%c\n", cmd, optopt); + return EXIT_FAILURE; + case '?': + fprintf(stderr, "%s: Unrecognized option -%c\n", cmd, optopt); + return EXIT_FAILURE; + } + } + + if (threads <= 0) { + threads = xsysconf(_SC_NPROCESSORS_ONLN); + if (threads > 8) { + threads = 8; + } + } + if (threads < 2) { + threads = 2; + } + --threads; + + // Listen for ^C to print the summary + struct sighook *hook = sighook(SIGINT, ctrlc, NULL, SH_CONTINUE | SH_ONESHOT); + + printf("I/O queue benchmark (%s)\n\n", bfs_version); + + printf("[-d] depth: %ld\n", depth); + printf("[-j] threads: %ld (including main)\n", threads + 1); + if (type == IOQ_NOP_HEAVY) { + printf("[-H] type: heavy (with syscalls)\n"); + } else { + printf("[-L] type: light (no syscalls)\n"); + } + printf("\n"); + + printf(" Time (s) │ Throughput (IO/s) │ Latency (ns/IO)\n"); + printf("══════════╪═══════════════════╪═════════════════\n"); + fflush(stdout); + + struct ioq *ioq = ioq_create(depth, threads); + bfs_everify(ioq, "ioq_create(%ld, %ld)", depth, threads); + + // Pre-allocate all the requests + while (ioq_capacity(ioq) > 0) { + int ret = ioq_nop(ioq, type, NULL); + bfs_everify(ret == 0, "ioq_nop(%d)", (int)type); + } + while (true) { + struct ioq_ent *ent = ioq_pop(ioq, true); + if (!ent) { + break; + } + ioq_free(ioq, ent); + } + + struct times total, lap; + times_init(&total); + lap = total; + + long seconds = 0; + while (!load(&quit, relaxed)) { + bool was_timing = lap.timing; + + for (int i = 0; i < 16; ++i) { + bool block = ioq_capacity(ioq) == 0; + if (!pop(ioq, &lap, block)) { + break; + } + } + + if (was_timing && !lap.timing) { + struct timespec elapsed; + gettime(&elapsed); + timespec_sub(&elapsed, &total.start); + + if (elapsed.tv_sec > seconds) { + seconds = elapsed.tv_sec; + times_print(&lap, seconds); + times_lap(&total, &lap); + } + + double ns = timespec_ns(&elapsed); + if (timeout > 0 && ns >= timeout * 1.0e9) { + break; + } + } + + for (int i = 0; i < 8; ++i) { + if (!push(ioq, type, &lap)) { + break; + } + } + } + + while (pop(ioq, &lap, true)); + times_lap(&total, &lap); + + if (load(&quit, relaxed)) { + printf("\r────^C────┼───────────────────┼─────────────────\n"); + } else { + printf("──────────┼───────────────────┼─────────────────\n"); + } + times_print(&total, 0); + + ioq_destroy(ioq); + sigunhook(hook); + return 0; +} |