diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..5f8df3c8c9e8490845248e33e3a7cdf1e20c89a7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +--- +BasedOnStyle: Google +ColumnLimit: 100 +TabWidth: 2 +UseTab: Always diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000000000000000000000000000000000000..b914e66b2dbd60a60b3c86493b0f09439fe15064 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,24 @@ +Checks: > + bugprone-*, + cert-*, + linuxkernel-*, + modernize-*, + performance-*, + portability-*, + readability-*, + -cert-err58-cpp, + -clang-diagnostic-empty-translation-unit, + -readability-braces-around-statements, + -readability-function-cognitive-complexity, + -readability-implicit-bool-conversion, + -readability-isolate-declaration, + -readability-magic-numbers, + +WarningsAsErrors: > + bugprone-*, + modernize-*, + clang-*, + readability-*, + performance-*, + +HeaderFilterRegex: .* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 550e2f028887b50da3420a13f510a83ab8e77eef..3efa392f146c8d902d48d66e88669bb74d9d4a48 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,9 @@ image: "flowdalic/debian-testing-dev:1.14" before_script: - | + apt install -y bear readarray TOOLS <<EOF + bear cc gcc EOF @@ -14,3 +16,11 @@ before_script: test-eval: script: - make + +tidy: + script: + - bear -- make all && make tidy + +check-format: + script: + - make check-format diff --git a/Makefile b/Makefile index 0e6ff8ceca944f13dd2f072bcde102bfd3c9a813..a9ced29e266a8d583c161a584437764298764a16 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,14 @@ BENCH_MAIN := bench.c -SYSCALLS := blocking io-uring io-uring-sqpoll epoll aio-sig aio-thrd +SYSCALLS := blocking io-uring io-uring-sqpoll io-uring-no-syscall epoll aio-sig aio-thrd OBJ := $(addprefix bench-,$(SYSCALLS)) LDFLAGS := -luring -pthread -lrt -# CFLAGS := -Werror -Wall -g -O3 -CFLAGS := -Werror -Wall -g -O0 +CFLAGS := -Werror -Wall -g -O3 +# CFLAGS := -Werror -Wall -g -O0 -.PHONY: all clean eval docker-eval +.PHONY: all clean eval docker-eval check eval: all @for syscall in $(SYSCALLS); do echo -n "$$syscall " ; ./bench-$$syscall; done @@ -19,7 +19,7 @@ docker-eval: all: $(OBJ) define generateTargets -bench-$(1): $(1).c bench.c | Makefile +bench-$(1): $(1).c bench.c stopwatch.c | Makefile $(CC) $(CFLAGS) -o $$@ $$^ $(LDFLAGS) endef @@ -27,3 +27,14 @@ $(foreach syscall,$(SYSCALLS),$(eval $(call generateTargets,$(syscall)))) clean: rm -f $(OBJ) + +check: tidy check-format + +tidy: + clang-tidy *.c + +check-format: + tools/check-format + +format: + clang-format -i *.c *.h diff --git a/aio-sig.c b/aio-sig.c index fda8c7365a0fc404f9da513027b1054d9ceb83a8..5c115e80263bb82cbba7241ee82d8987eea7eb0b 100644 --- a/aio-sig.c +++ b/aio-sig.c @@ -1,29 +1,24 @@ #include <aio.h> #include <err.h> -#include <semaphore.h> #include <signal.h> +#include <stdatomic.h> #include <stdint.h> #include <stdlib.h> -#include "rdtsc.h" +#include "common.h" +#include "stopwatch.h" -int64_t clock_before, clock_after; struct aiocb aiocb; -sem_t sem; +atomic_int done; -void callback(int sig, siginfo_t* info, void* context) { - clock_after = rdtsc_s(); - if (info->si_value.sival_int != 42) - errx(EXIT_FAILURE, "got unexpected sigval value"); +void callback(int sig, siginfo_t *info, void *context) { + if (unlikely(info->si_value.sival_int != 42)) errx(EXIT_FAILURE, "got unexpected sigval value"); - sem_post(&sem); + atomic_store(&done, 1); } void init(int fd) { - if (sem_init(&sem, 0, 0) == -1) - err(EXIT_FAILURE, "sem_init failed"); - aiocb.aio_fildes = fd; aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL; aiocb.aio_sigevent.sigev_signo = SIGUSR1; @@ -33,20 +28,20 @@ void init(int fd) { sa.sa_sigaction = callback; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; - if (sigaction(SIGUSR1, &sa, NULL) == -1) - err(EXIT_FAILURE, "sigaction failed"); + if (sigaction(SIGUSR1, &sa, NULL) == -1) err(EXIT_FAILURE, "sigaction failed"); } -int64_t do_write(int fd, const void* buf, size_t count) { - aiocb.aio_buf = (void*)buf; +void do_write(int fd, const void *buf, size_t count) { + aiocb.aio_buf = (void *)buf; aiocb.aio_nbytes = count; - clock_before = rdtsc_s(); + start_watch(); int res = aio_write(&aiocb); - sem_wait(&sem); - if (res == -1) - err(EXIT_FAILURE, "aio_write failed"); + if (unlikely(res == -1)) err(EXIT_FAILURE, "aio_write failed"); - return clock_after - clock_before; + while (!atomic_load(&done)) { + } + stop_watch(); + atomic_store(&done, 1); } diff --git a/aio-thrd.c b/aio-thrd.c index 422917f35b116cd560ce3e7cf38c54102cc2c4d3..ef7c36508e9ae140c8a8e80fdbeab2c92f7911d4 100644 --- a/aio-thrd.c +++ b/aio-thrd.c @@ -1,46 +1,40 @@ #include <aio.h> #include <err.h> -#include <semaphore.h> #include <signal.h> +#include <stdatomic.h> #include <stdint.h> #include <stdlib.h> -#include "rdtsc.h" +#include "stopwatch.h" -int64_t clock_before, clock_after; -struct aiocb aiocb; - -sem_t sem; +atomic_int done; +struct aiocb aiocb; void callback(union sigval sigval) { - clock_after = rdtsc_s(); + stop_watch(); - if (sigval.sival_int != 42) - errx(EXIT_FAILURE, "got unexpected sigval value"); + if (sigval.sival_int != 42) errx(EXIT_FAILURE, "got unexpected sigval value"); - sem_post(&sem); + atomic_store(&done, 1); } void init(int fd) { - if (sem_init(&sem, 0, 0) == -1) - err(EXIT_FAILURE, "sem_init failed"); - aiocb.aio_fildes = fd; aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD; aiocb.aio_sigevent.sigev_notify_function = callback; aiocb.aio_sigevent.sigev_value.sival_int = 42; } -int64_t do_write(int fd, void* buf, size_t count) { +void do_write(int fd, void *buf, size_t count) { aiocb.aio_buf = buf; aiocb.aio_nbytes = count; - clock_before = rdtsc_s(); + start_watch(); int res = aio_write(&aiocb); - sem_wait(&sem); - if (res == -1) - err(EXIT_FAILURE, "aio_write failed"); + while (!atomic_load(&done)) { + } + atomic_store(&done, 0); - return clock_after - clock_before; + if (res == -1) err(EXIT_FAILURE, "aio_write failed"); } diff --git a/bench.c b/bench.c index 861ea702cf28a4ecefbdcf77a78a060dce92e79f..b7439a6778522fdcc0a15b1f6cb0c9ea8176fa76 100644 --- a/bench.c +++ b/bench.c @@ -5,22 +5,16 @@ #include <stdlib.h> #include <sys/eventfd.h> +#include "stopwatch.h" + void init(int fd); -int64_t do_write(int fd, const void* buf, size_t count); +void do_write(int fd, const void *buf, size_t count); const size_t warmup = 10000; const size_t iterations = 1000000; #define BUFSIZE 64 -/* static int open_dev_zero() { */ - /* int fd = open("/dev/zero", O_RDONLY); */ - /* if (fd == -1) { */ - /* err(EXIT_FAILURE, "opening /dev/zero failed"); */ - /* } */ - /* return fd; */ -/* } */ - static int create_eventfd() { int fd = eventfd(0, 0); if (fd == -1) { @@ -30,25 +24,30 @@ static int create_eventfd() { } int main() { - double avg_cycles = 0; + uint64_t cycles_sum = 0; + uint64_t nanos_sum = 0; + uint64_t write_buf = 1; - /* int fd = open_dev_zero(); */ int fd = create_eventfd(); init(fd); - /* char read_buf[BUFSIZE]; */ - uint64_t write_buf = 1; + for (size_t i = 0; i < warmup; ++i) do_write(fd, &write_buf, sizeof(write_buf)); - for (size_t i = 0; i < warmup; ++i) + for (int64_t i = 1; i <= iterations; ++i) { do_write(fd, &write_buf, sizeof(write_buf)); - - for (size_t i = 1; i <= iterations; ++i) { - ssize_t cycles = do_write(fd, &write_buf, sizeof(write_buf)); - avg_cycles += ((double)cycles - avg_cycles) / i; + if (__builtin_add_overflow(nanos_sum, clock_diff_nanos(), &nanos_sum)) + errx(EXIT_FAILURE, "nanos overflowed at %ld", i); + if (__builtin_add_overflow(cycles_sum, clock_diff_cycles(), &cycles_sum)) + errx(EXIT_FAILURE, "cycles overflowed at %ld", i); } - printf("avg_cycles: %lf\n", avg_cycles); + // Since uint64_t <-> double conversion are not well defined + // and we use really small units (cycles and nanoseconds) I am willing + // to accept that we throw away anything after the decimal point. + uint64_t avg_nanos = nanos_sum / iterations; + uint64_t avg_cycles = cycles_sum / iterations; + printf("%lu ns, %lu cycles\n", avg_nanos, avg_cycles); return 0; } diff --git a/blocking.c b/blocking.c index 9e057146d9850c74d25c7ae7d9bff98e791aca93..3f3cdbdb44a5baf3d0b4c27bf4c67dd812655423 100644 --- a/blocking.c +++ b/blocking.c @@ -2,19 +2,14 @@ #include <stdlib.h> #include <unistd.h> -#include "rdtsc.h" +#include "stopwatch.h" void init(__attribute__((unused)) int fd) {} -int64_t do_write(int fd, const void* buf, size_t count) { - int64_t clock_before, clock_after; - - clock_before = rdtsc_s(); +void do_write(int fd, const void *buf, size_t count) { + start_watch(); ssize_t res = write(fd, buf, count); - clock_after = rdtsc_s(); - - if (res == -1) - err(EXIT_FAILURE, "write failed"); + stop_watch(); - return clock_after - clock_before; + if (res == -1) err(EXIT_FAILURE, "write failed"); } diff --git a/common.h b/common.h index 909aa54da958bc60b92ee9facccad859ff77ca9c..b8acbfda87da27e7e0953a71780e913394bc80ca 100644 --- a/common.h +++ b/common.h @@ -1,22 +1,3 @@ -#include <err.h> -#include <fcntl.h> -#include <stdint.h> -#include <stdlib.h> +#pragma once -#define BUFSIZE 64 - -int64_t do_write(int fd, const void* buf, size_t count); - -__inline__ int64_t rdtsc_s(void) { - unsigned a, d; - asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx"); - asm volatile("rdtsc" : "=a" (a), "=d" (d)); - return ((unsigned long)a) | (((unsigned long)d) << 32); -} - -__inline__ int64_t rdtsc_e(void) { - unsigned a, d; - asm volatile("rdtscp" : "=a" (a), "=d" (d)); - asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx"); - return ((unsigned long)a) | (((unsigned long)d) << 32); -} +#define unlikely(x) __builtin_expect(!!(x), 0) diff --git a/epoll.c b/epoll.c index d898734ee9a9203afe743b135d605e6202f98d12..dd27dd8ab48a010aa7209de17ac4d20e923d9ae3 100644 --- a/epoll.c +++ b/epoll.c @@ -3,40 +3,30 @@ #include <sys/epoll.h> #include <unistd.h> -#include "rdtsc.h" +#include "stopwatch.h" struct epoll_event ev; int nfds, epollfd; void init(int fd) { epollfd = epoll_create1(0); - if (epollfd == -1) - err(EXIT_FAILURE, "creating epoll failed"); + if (epollfd == -1) err(EXIT_FAILURE, "creating epoll failed"); ev.events = EPOLLOUT; ev.data.fd = fd; - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) - err(EXIT_FAILURE, "epoll_ctl failed"); - + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) err(EXIT_FAILURE, "epoll_ctl failed"); } -int64_t do_write(int fd, const void* buf, size_t count) { - int64_t clock_before, clock_after; - - clock_before = rdtsc_s(); +void do_write(int fd, const void *buf, size_t count) { + start_watch(); nfds = epoll_wait(epollfd, &ev, 1, -1); size_t res = write(fd, buf, count); - clock_after = rdtsc_s(); - - if (nfds == -1) - err(EXIT_FAILURE, "epoll_wait failed"); + stop_watch(); - if (ev.data.fd != fd) - errx(EXIT_FAILURE, "got unexpected fd from epoll"); + if (nfds == -1) err(EXIT_FAILURE, "epoll_wait failed"); - if (res == -1) - err(EXIT_FAILURE, "write failed"); + if (ev.data.fd != fd) errx(EXIT_FAILURE, "got unexpected fd from epoll"); - return clock_after - clock_before; + if (res == -1) err(EXIT_FAILURE, "write failed"); } diff --git a/io-uring-no-syscall.c b/io-uring-no-syscall.c new file mode 100644 index 0000000000000000000000000000000000000000..62f7c5a257cce2b6fdc146660b8968082736c798 --- /dev/null +++ b/io-uring-no-syscall.c @@ -0,0 +1,42 @@ +#include <err.h> +#include <errno.h> +#include <liburing.h> +#include <stdlib.h> + +#include "common.h" +#include "stopwatch.h" + +struct io_uring ring; + +void init(__attribute__((unused)) int fd) { + int res = io_uring_queue_init(16, &ring, IORING_SETUP_SQPOLL); + if (res < 0) { + errno = res; + err(EXIT_FAILURE, "io_uring_setup failed"); + } +} + +void do_write(int fd, const void *buf, size_t count) { + start_watch(); + struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); + while (unlikely(!sqe)) { + sqe = io_uring_get_sqe(&ring); + } + io_uring_prep_write(sqe, fd, buf, count, 0); + + int res = io_uring_submit(&ring); + + struct io_uring_cqe *cqe; + while (io_uring_peek_cqe(&ring, &cqe) == -EAGAIN) { + } + + stop_watch(); + + if (res < 0) { + err(EXIT_FAILURE, "io_submit failed"); + } + + if (cqe->res < 0) { + err(EXIT_FAILURE, "write request failed"); + } +} diff --git a/io-uring-sqpoll.c b/io-uring-sqpoll.c index e46e51706e79a30323c56289d7a0e983e7a6f7d7..349f71ff4266e9469b7cc1b5773904524ed6c4a0 100644 --- a/io-uring-sqpoll.c +++ b/io-uring-sqpoll.c @@ -3,9 +3,8 @@ #include <liburing.h> #include <stdlib.h> -#define unlikely(x) __builtin_expect(!!(x), 0) - -#include "rdtsc.h" +#include "common.h" +#include "stopwatch.h" struct io_uring ring; @@ -17,9 +16,8 @@ void init(__attribute__((unused)) int fd) { } } -int64_t do_write(int fd, const void* buf, size_t count) { - int64_t clock_before, clock_after; - clock_before = rdtsc_s(); +void do_write(int fd, const void *buf, size_t count) { + start_watch(); struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); while (unlikely(!sqe)) { @@ -29,13 +27,13 @@ int64_t do_write(int fd, const void* buf, size_t count) { int res = io_uring_submit_and_wait(&ring, 1); - clock_after = rdtsc_e(); + stop_watch(); if (res < 0) { err(EXIT_FAILURE, "io_uring_submit_and_wait failed"); } - struct io_uring_cqe* cqe; + struct io_uring_cqe *cqe; res = io_uring_peek_cqe(&ring, &cqe); if (res < 0) { err(EXIT_FAILURE, "io_uring_peek_cqe failed"); @@ -44,6 +42,4 @@ int64_t do_write(int fd, const void* buf, size_t count) { if (cqe->res < 0) { err(EXIT_FAILURE, "write request failed"); } - - return clock_after - clock_before; } diff --git a/io-uring.c b/io-uring.c index 53f16f1d745450432f8fdc20a1efcb9c35c50c24..ea0141eb2f8a09a3e7ce45c12458227a2e66ff68 100644 --- a/io-uring.c +++ b/io-uring.c @@ -3,7 +3,7 @@ #include <liburing.h> #include <stdlib.h> -#include "rdtsc.h" +#include "stopwatch.h" struct io_uring ring; @@ -15,30 +15,21 @@ void init(__attribute__((unused)) int fd) { } } -int64_t do_write(int fd, const void* buf, size_t count) { - int64_t clock_before, clock_after; - clock_before = rdtsc_s(); +void do_write(int fd, const void *buf, size_t count) { + start_watch(); struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); io_uring_prep_write(sqe, fd, buf, count, 0); int res = io_uring_submit_and_wait(&ring, 1); - clock_after = rdtsc_e(); + stop_watch(); - if (res < 0) { - err(EXIT_FAILURE, "io_uring_submit_and_wait failed"); - } + if (res < 0) err(EXIT_FAILURE, "io_uring_submit_and_wait failed"); - struct io_uring_cqe* cqe; + struct io_uring_cqe *cqe; res = io_uring_peek_cqe(&ring, &cqe); - if (res < 0) { - err(EXIT_FAILURE, "io_uring_peek_cqe failed"); - } - - if (cqe->res < 0) { - err(EXIT_FAILURE, "write request failed"); - } + if (res < 0) err(EXIT_FAILURE, "io_uring_peek_cqe failed"); - return clock_after - clock_before; + if (cqe->res < 0) err(EXIT_FAILURE, "write request failed"); } diff --git a/rdtsc.h b/rdtsc.h deleted file mode 100644 index ac687bb6762c64e94bffb91b65cf0f7857987672..0000000000000000000000000000000000000000 --- a/rdtsc.h +++ /dev/null @@ -1,15 +0,0 @@ -#include <stdint.h> - -static __inline__ int64_t rdtsc_s(void) { - unsigned a, d; - asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx"); - asm volatile("rdtsc" : "=a" (a), "=d" (d)); - return ((unsigned long)a) | (((unsigned long)d) << 32); -} - -static __inline__ int64_t rdtsc_e(void) { - unsigned a, d; - asm volatile("rdtscp" : "=a" (a), "=d" (d)); - asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx"); - return ((unsigned long)a) | (((unsigned long)d) << 32); -} diff --git a/stopwatch.c b/stopwatch.c new file mode 100644 index 0000000000000000000000000000000000000000..472baf59370274ea7880e627ed6fe96d9f74c5b7 --- /dev/null +++ b/stopwatch.c @@ -0,0 +1,16 @@ +#include "stopwatch.h" + +uint64_t cycles_start, cycles_stop; +struct timespec start, stop; + +int64_t sec_to_nanos(int64_t sec) { return sec * 1000 * 1000 * 1000; } + +double nanos_to_millis(int64_t nanos) { return ((double)nanos) / (1000 * 1000); } + +int64_t clock_diff_nanos() { + int64_t nanos = sec_to_nanos(stop.tv_sec - start.tv_sec); + nanos += (stop.tv_nsec - start.tv_nsec); + return nanos; +} + +uint64_t clock_diff_cycles() { return cycles_stop - cycles_start; } diff --git a/stopwatch.h b/stopwatch.h new file mode 100644 index 0000000000000000000000000000000000000000..7b670d132102574a27c90b9753a20ca6a486c653 --- /dev/null +++ b/stopwatch.h @@ -0,0 +1,36 @@ +#pragma once + +#include <stdint.h> +#include <time.h> + +extern uint64_t cycles_start, cycles_stop; +extern struct timespec start, stop; + +static __inline__ uint64_t rdtsc_s(void) { + unsigned a, d; + asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx"); + asm volatile("rdtsc" : "=a"(a), "=d"(d)); + return ((unsigned long)a) | (((unsigned long)d) << 32); +} + +static __inline__ uint64_t rdtsc_e(void) { + unsigned a, d; + asm volatile("rdtscp" : "=a"(a), "=d"(d)); + asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx"); + return ((unsigned long)a) | (((unsigned long)d) << 32); +} + +static inline void start_watch() { + clock_gettime(CLOCK_MONOTONIC, &start); + cycles_start = rdtsc_s(); +} + +static inline void stop_watch() { + cycles_stop = rdtsc_e(); + clock_gettime(CLOCK_MONOTONIC, &stop); +} + +uint64_t clock_diff_cycles(); +int64_t clock_diff_nanos(); +double nanos_to_millis(int64_t nanos); +int64_t sec_to_nanos(int64_t sec); diff --git a/tools/check-format b/tools/check-format new file mode 100755 index 0000000000000000000000000000000000000000..005c77c1ecfef05fc891dbb6ffc2d3c90a2a6912 --- /dev/null +++ b/tools/check-format @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Pretty fancy method to get reliable the absolute path of a shell +# script, *even if it is sourced*. Credits go to GreenFox on +# stackoverflow: http://stackoverflow.com/a/12197518/194894 +pushd . > /dev/null +SCRIPTDIR="${BASH_SOURCE[0]}"; +while([ -h "${SCRIPTDIR}" ]); do + cd "`dirname "${SCRIPTDIR}"`" + SCRIPTDIR="$(readlink "`basename "${SCRIPTDIR}"`")"; +done +cd "`dirname "${SCRIPTDIR}"`" > /dev/null +SCRIPTDIR="`pwd`"; +popd > /dev/null + +DEBUG=false +while getopts d OPT; do + case $OPT in + d) + set -x + DEBUG=true + ;; + *) + echo "usage: ${0##*/} [-dq} [--] ARGS..." + exit 2 + esac +done +shift $(( OPTIND - 1 )) +OPTIND=1 + +ROOTDIR=$(readlink -f "${SCRIPTDIR}/..") + +MAX_PROCS=$(nproc) + +CHECKED_FILES_FILE=$(mktemp) +if ! $DEBUG; then + trap 'rm "${CHECKED_FILES_FILE}"' EXIT +fi + +cd "${ROOTDIR}" +# Note that the --dry-run and --Werror clang-format arguments require +# clang-format 10 or higher. See https://reviews.llvm.org/D68554 +find . \( -path '*/\.*' -o -path "./subprojects*" -o -path "./build*" \) -prune -o \ + -type f -regextype posix-extended -regex '.*\.(c|h|cpp|hpp)' -print0 |\ + tee "${CHECKED_FILES_FILE}" |\ + xargs --null --max-args=3 --max-procs="${MAX_PROCS}" \ + clang-format --style=file --dry-run -Werror + +FILE_COUNT=$(<"${CHECKED_FILES_FILE}" tr -cd '\0' | wc -c) +echo "Checked ${FILE_COUNT} files for format violations"