From 6417bc131cb65052ec271a77879fc100bd677fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Wed, 24 Mar 2021 17:52:56 +0100 Subject: [PATCH] WIP: Add pause.h --- lib/isc/Makefile.am | 1 + lib/isc/include/isc/pause.h | 41 ++++++ lib/isc/tests/mutex_test.c | 248 ++++++++++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 lib/isc/include/isc/pause.h create mode 100644 lib/isc/tests/mutex_test.c diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am index 71712754c1..aeebf0a225 100644 --- a/lib/isc/Makefile.am +++ b/lib/isc/Makefile.am @@ -57,6 +57,7 @@ libisc_la_HEADERS = \ include/isc/nonce.h \ include/isc/os.h \ include/isc/parseint.h \ + include/isc/pause.h \ include/isc/pool.h \ include/isc/portset.h \ include/isc/platform.h \ diff --git a/lib/isc/include/isc/pause.h b/lib/isc/include/isc/pause.h new file mode 100644 index 0000000000..5b840d7f48 --- /dev/null +++ b/lib/isc/include/isc/pause.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#if defined(_MSC_VER) +#include +#define isc__pause() YieldProcessor() +#elif defined(__x86_64__) +#include +#define isc__pause() _mm_pause() +#elif defined(__i386__) +#define isc__pause() __asm__ __volatile__("rep; nop") +#elif defined(__ia64__) +#define isc__pause() __asm__ __volatile__("hint @pause") +#elif defined(__arm__) && HAVE_ARM_YIELD +#define isc__pause() __asm__ __volatile__("yield") +#elif defined(sun) && (defined(__sparc) || defined(__sparc__)) +#include +#define isc__pause() smt_pause() +#elif (defined(__sparc) || defined(__sparc__)) && HAVE_SPARC_PAUSE +#define isc__pause() __asm__ __volatile__("pause") +#elif defined(__ppc__) || defined(_ARCH_PPC) || defined(_ARCH_PWR) || \ + defined(_ARCH_PWR2) || defined(_POWER) +#define isc__pause() __asm__ volatile("or 27,27,27") +#else /* if defined(_MSC_VER) */ +#define isc__pause() sched_yield() +#endif /* if defined(_MSC_VER) */ + +#define isc_pause(iters) \ + for (size_t __pause = 0; __pause < iters; __pause++) { \ + isc__pause(); \ + } diff --git a/lib/isc/tests/mutex_test.c b/lib/isc/tests/mutex_test.c new file mode 100644 index 0000000000..3deea3166e --- /dev/null +++ b/lib/isc/tests/mutex_test.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isctest.h" + +#define LOOPS 1000 +#define DELAY_LOOP 100 + +static unsigned int workers = 0; + +static int +_setup(void **state) { + isc_result_t result; + char *p; + + if (workers == 0) { + workers = isc_os_ncpus(); + } + + p = getenv("ISC_TASK_WORKERS"); + if (p != NULL) { + workers = atoi(p); + } + INSIST(workers != 0); + + UNUSED(state); + + result = isc_test_begin(NULL, true, workers); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +static void +isc_mutex_test(void **state) { + isc_mutex_t lock; + + UNUSED(state); + + isc_mutex_init(&lock); + + for (size_t i = 0; i < LOOPS; i++) { + isc_mutex_lock(&lock); + isc_pause(DELAY_LOOP); + isc_mutex_unlock(&lock); + } + + isc_mutex_destroy(&lock); +} + +#define ITERS 20 + +#define DC 200 +#define MIN 800 +#define MAX 1600 + +static size_t shared_counter = 0; +static size_t expected_counter = SIZE_MAX; +static isc_mutex_t lock = ISC_MUTEX_INITIALIZER; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +static isc_threadresult_t +pthread_mutex_thread(isc_threadarg_t arg) { + size_t cont = *(size_t *)arg; + + for (size_t i = 0; i < LOOPS; i++) { + pthread_mutex_lock(&mutex); + size_t v = shared_counter; + isc_pause(DELAY_LOOP); + shared_counter = v + 1; + pthread_mutex_unlock(&mutex); + isc_pause(cont); + } + + return ((isc_threadresult_t)0); +} + +static isc_threadresult_t +isc_mutex_thread(isc_threadarg_t arg) { + size_t cont = *(size_t *)arg; + + for (size_t i = 0; i < LOOPS; i++) { + isc_mutex_lock(&lock); + size_t v = shared_counter; + isc_pause(DELAY_LOOP); + shared_counter = v + 1; + isc_mutex_unlock(&lock); + isc_pause(cont); + } + + return ((isc_threadresult_t)0); +} + +static void +isc_mutex_benchmark(void **state) { + isc_thread_t *threads = isc_mem_get(test_mctx, + sizeof(*threads) * workers); + isc_time_t ts1, ts2; + double t; + isc_result_t result; + int dc; + size_t cont; + + UNUSED(state); + + memset(threads, 0, sizeof(*threads) * workers); + + expected_counter = ITERS * workers * LOOPS * ((MAX - MIN) / DC + 1); + + /* PTHREAD MUTEX */ + + result = isc_time_now_hires(&ts1); + assert_int_equal(result, ISC_R_SUCCESS); + + shared_counter = 0; + dc = DC; + for (size_t l = 0; l < ITERS; l++) { + for (cont = (dc > 0) ? MIN : MAX; cont <= MAX && cont >= MIN; + cont += dc) { + for (size_t i = 0; i < workers; i++) { + isc_thread_create(pthread_mutex_thread, &cont, + &threads[i]); + } + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + } + dc = -dc; + } + assert_int_equal(shared_counter, expected_counter); + + result = isc_time_now_hires(&ts2); + assert_int_equal(result, ISC_R_SUCCESS); + + t = isc_time_microdiff(&ts2, &ts1); + + printf("[ TIME ] isc_mutex_benchmark: %zu pthread_mutex loops in " + "%u threads, %2.3f seconds, %2.3f calls/second\n", + shared_counter, workers, t / 1000000.0, + shared_counter / (t / 1000000.0)); + + /* ISC MUTEX */ + + result = isc_time_now_hires(&ts1); + assert_int_equal(result, ISC_R_SUCCESS); + + dc = DC; + shared_counter = 0; + for (size_t l = 0; l < ITERS; l++) { + for (cont = (dc > 0) ? MIN : MAX; cont <= MAX && cont >= MIN; + cont += dc) { + for (size_t i = 0; i < workers; i++) { + isc_thread_create(isc_mutex_thread, &cont, + &threads[i]); + } + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + } + dc = -dc; + } + assert_int_equal(shared_counter, expected_counter); + + result = isc_time_now_hires(&ts2); + assert_int_equal(result, ISC_R_SUCCESS); + + t = isc_time_microdiff(&ts2, &ts1); + + printf("[ TIME ] isc_mutex_benchmark: %zu isc_mutex loops in %u " + "threads, %2.3f seconds, %2.3f calls/second\n", + shared_counter, workers, t / 1000000.0, + shared_counter / (t / 1000000.0)); + + isc_mem_put(test_mctx, threads, sizeof(*threads) * workers); +} + +/* + * Main + */ + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(isc_mutex_test, _setup, + _teardown), +#if !defined(__SANITIZE_THREAD__) + cmocka_unit_test_setup_teardown(isc_mutex_benchmark, _setup, + _teardown), +#endif /* __SANITIZE_THREAD__ */ + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */