From ff706eb55fc98579d1e0018f303c8afa4db3c56d Mon Sep 17 00:00:00 2001 From: niels-vdp Date: Sun, 1 Jun 2025 11:56:47 +0100 Subject: [PATCH 1/7] examples/tilt: updated campaigns&benchmarks, implemenation for reciprocating locks --- examples/tilt/.gitignore | 1 + examples/tilt/bench/CMakeLists.txt | 19 +- examples/tilt/bench/atomic_bench.cpp | 71 +++++ examples/tilt/bench/include/config.h.in | 13 + examples/tilt/bench/mutex_bench.c | 56 ++++ examples/tilt/bench/mutex_bench.cpp | 79 ++++++ examples/tilt/bench/{mutex.c => mutex_test.c} | 0 examples/tilt/bench/mutexbench_moderate.cpp | 64 +++++ examples/tilt/campaign_tilt.py | 173 ++++++++++-- examples/tilt/deps/reciplock/CMakeLists.txt | 10 + .../tilt/deps/reciplock/include/reciplock.h | 248 ++++++++++++++++++ examples/tilt/kit/mutex_bench.py | 152 +++++++++++ examples/tilt/kit/test_bench.py | 138 ++++++++++ examples/tilt/kit/tiltlib.py | 247 ++++++----------- examples/tilt/locks/CMakeLists.txt | 13 + examples/tilt/locks/clhlock.c | 37 +++ examples/tilt/locks/hemlock.c | 38 +++ examples/tilt/locks/mcslock.c | 37 +++ examples/tilt/locks/reciplock.c | 32 +++ examples/tilt/locks/ticketlock.c | 32 +++ examples/tilt/locks/twalock.c | 35 +++ 21 files changed, 1293 insertions(+), 202 deletions(-) create mode 100644 examples/tilt/.gitignore create mode 100644 examples/tilt/bench/atomic_bench.cpp create mode 100644 examples/tilt/bench/include/config.h.in create mode 100644 examples/tilt/bench/mutex_bench.c create mode 100644 examples/tilt/bench/mutex_bench.cpp rename examples/tilt/bench/{mutex.c => mutex_test.c} (100%) create mode 100644 examples/tilt/bench/mutexbench_moderate.cpp create mode 100644 examples/tilt/deps/reciplock/CMakeLists.txt create mode 100644 examples/tilt/deps/reciplock/include/reciplock.h create mode 100644 examples/tilt/kit/mutex_bench.py create mode 100644 examples/tilt/kit/test_bench.py create mode 100644 examples/tilt/locks/clhlock.c create mode 100644 examples/tilt/locks/hemlock.c create mode 100644 examples/tilt/locks/mcslock.c create mode 100644 examples/tilt/locks/reciplock.c create mode 100644 examples/tilt/locks/ticketlock.c create mode 100644 examples/tilt/locks/twalock.c diff --git a/examples/tilt/.gitignore b/examples/tilt/.gitignore new file mode 100644 index 00000000..01eaf832 --- /dev/null +++ b/examples/tilt/.gitignore @@ -0,0 +1 @@ +/*/build-*/* \ No newline at end of file diff --git a/examples/tilt/bench/CMakeLists.txt b/examples/tilt/bench/CMakeLists.txt index 85445cb2..bf8ecff3 100644 --- a/examples/tilt/bench/CMakeLists.txt +++ b/examples/tilt/bench/CMakeLists.txt @@ -1,9 +1,24 @@ +# Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +# SPDX-License-Identifier: MIT + cmake_minimum_required(VERSION 3.10) project(PthreadMutexExample) +# Build options +set(NB_THREADS 4 CACHE STRING "Number of threads increasing the counter") +set(RUN_DURATION_SECONDS 10 CACHE STRING "Time during which the threads will increase the counter") + set(CMAKE_C_STANDARD 99) -add_executable(mutex mutex.c) +configure_file(${CMAKE_SOURCE_DIR}/include/config.h.in ${CMAKE_BINARY_DIR}/include/config.h) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + +add_executable(mutex_test mutex_test.c) +# add_executable(atomic_bench atomic_bench.cpp) +add_executable(mutex_bench mutex_bench.cpp) find_package(Threads REQUIRED) -target_link_libraries(mutex Threads::Threads) +target_link_libraries(mutex_test Threads::Threads) +target_link_libraries(mutex_bench Threads::Threads) +# target_link_libraries(atomic_bench Threads::Threads) diff --git a/examples/tilt/bench/atomic_bench.cpp b/examples/tilt/bench/atomic_bench.cpp new file mode 100644 index 00000000..36b07dbf --- /dev/null +++ b/examples/tilt/bench/atomic_bench.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THREAD_COUNT 8 +#define BENCH_DURATION_SEC 10 + +#define IMPLICIT_INIT + +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +struct S { + uint32_t a, b, c, d, e; +}; +std::atomic shared; + +std::atomic done{false}; +std::atomic iterations_total{0}; + +void* worker(void* arg) { + S local = {1, 2, 3, 4, 5}; + uint64_t iterations_local = 0; + while (!atomic_load_explicit(&done, std::memory_order_relaxed)) { + pthread_mutex_lock(&lock); + // Critical section + atomic_exchange_explicit(&shared, local, std::memory_order_seq_cst); // exchange with global + + pthread_mutex_unlock(&lock); + + // Non-critical section + // (sleep or dummy ops to simulate delay) + ++iterations_local; + } + atomic_fetch_add_explicit(&iterations_total, iterations_local, std::memory_order_relaxed); + return NULL; +} + +int main() { + #if !defined(IMPLICIT_INIT) + if (pthread_mutex_init(&lock, NULL) != 0) { + printf("Mutex initialization failed\n"); + return 1; + } + #endif /* EXPLICIT_INIT */ + + // Initialize shared atomic with zeros + //atomic_store_explicit(&shared, {0, 0, 0, 0, 0}, std::memory_order_relaxed); + + pthread_t threads[THREAD_COUNT]; + for (int i = 0; i < THREAD_COUNT; ++i) + pthread_create(&threads[i], NULL, worker, NULL); + + //sleep(BENCH_DURATION_SEC); + std::this_thread::sleep_for(std::chrono::seconds(BENCH_DURATION_SEC)); + atomic_store_explicit(&done, 1, std::memory_order_relaxed); + + + for (int i = 0; i < THREAD_COUNT; ++i) + pthread_join(threads[i], NULL); + + std::cout << "Total iterations: " << iterations_total.load() << "\n"; + pthread_mutex_destroy(&lock); + + return 0; +} diff --git a/examples/tilt/bench/include/config.h.in b/examples/tilt/bench/include/config.h.in new file mode 100644 index 00000000..42742cb7 --- /dev/null +++ b/examples/tilt/bench/include/config.h.in @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. + * SPDX-License-Identifier: MIT + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#define NB_THREADS @NB_THREADS@ + +#define RUN_DURATION_SECONDS @RUN_DURATION_SECONDS@ + +#endif /* CONFIG_H */ diff --git a/examples/tilt/bench/mutex_bench.c b/examples/tilt/bench/mutex_bench.c new file mode 100644 index 00000000..1438a8df --- /dev/null +++ b/examples/tilt/bench/mutex_bench.c @@ -0,0 +1,56 @@ +#include +#include +#include + +#define THREAD_COUNT 8 +#define BENCH_DURATION_SEC 10 + +#define IMPLICIT_INIT + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +static atomic_bool done = false; +static atomic_uint_fast64_t iterations_total = 0; + +void* worker(void* arg) { + uint64_t iterations_local = 0; + while (!atomic_load_explicit(&done, memory_order_relaxed)) { + pthread_mutex_lock(&lock); + // Critical section + // (could do something tiny like a counter or memory op) + pthread_mutex_unlock(&lock); + + // Non-critical section + // (sleep or dummy ops to simulate delay) + ++iterations_local; + } + atomic_fetch_add(&iterations_total, iterations_local); + return NULL; +} + +int main() { + #if !defined(IMPLICIT_INIT) + if (pthread_mutex_init(&lock, NULL) != 0) { + printf("Mutex initialization failed\n"); + return 1; + } + #endif /* EXPLICIT_INIT */ + + pthread_t threads[THREAD_COUNT]; + + for (int i = 0; i < THREAD_COUNT; i++) { + pthread_create(&threads[i], NULL, worker, NULL); + } + + sleep(BENCH_DURATION_SEC); + atomic_store(&done, true); + + for (int i = 0; i < THREAD_COUNT; i++) { + pthread_join(threads[i], NULL); + } + + printf("Total iterations: %lu\n", atomic_load(&iterations_total)); + pthread_mutex_destroy(&lock); + + return 0; +} diff --git a/examples/tilt/bench/mutex_bench.cpp b/examples/tilt/bench/mutex_bench.cpp new file mode 100644 index 00000000..3b8b637e --- /dev/null +++ b/examples/tilt/bench/mutex_bench.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* defines NB_THREADS, RUN_DURATION_SECONDS and lock_* operations and types. */ + +// #define RUN_DURATION_SECONDS 5 +// #define NB_THREADS 8 + +#define IMPLICIT_INIT + +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +std::mt19937 prng{42}; // shared global PRNG +std::atomic done{false}; +std::atomic iterations_total{0}; + +void* worker(void* arg) { + uint64_t iterations_local = 0; + while (!atomic_load_explicit(&done, std::memory_order_relaxed)) { + pthread_mutex_lock(&lock); + // Critical section + prng(); // one step of PRNG in critical section + + pthread_mutex_unlock(&lock); + + // Non-critical section + // (sleep or dummy ops to simulate delay) + ++iterations_local; + } + atomic_fetch_add_explicit(&iterations_total, iterations_local, std::memory_order_relaxed); + void *result = (void*) iterations_local; + return result; +} + +int main() { + #if !defined(IMPLICIT_INIT) + if (pthread_mutex_init(&lock, NULL) != 0) { + printf("Mutex initialization failed\n"); + return 1; + } + #endif /* EXPLICIT_INIT */ + + pthread_t threads[NB_THREADS]; + unsigned long thread_iterations[NB_THREADS]; + for (int i = 0; i < NB_THREADS; ++i) + pthread_create(&threads[i], NULL, worker, NULL); + + //sleep(RUN_DURATION_SECONDS); + std::this_thread::sleep_for(std::chrono::seconds(RUN_DURATION_SECONDS)); + atomic_store_explicit(&done, 1, std::memory_order_relaxed); + + + for (int i = 0; i < NB_THREADS; ++i) { + void* return_value; + pthread_join(threads[i], &return_value); + thread_iterations[i] = (unsigned long) return_value; + } + + std::cout << "total_iterations=" << iterations_total.load(); + std::cout << ";duration=" << RUN_DURATION_SECONDS; + std::cout << ";nb_threads=" << NB_THREADS; + + // print per thread iterations + for (size_t k = 0u; k < NB_THREADS; ++k) { + std::cout << ";thread_" << k << "=" << thread_iterations[k]; + } + + std::cout << "\n"; + pthread_mutex_destroy(&lock); + + return 0; +} diff --git a/examples/tilt/bench/mutex.c b/examples/tilt/bench/mutex_test.c similarity index 100% rename from examples/tilt/bench/mutex.c rename to examples/tilt/bench/mutex_test.c diff --git a/examples/tilt/bench/mutexbench_moderate.cpp b/examples/tilt/bench/mutexbench_moderate.cpp new file mode 100644 index 00000000..a36befce --- /dev/null +++ b/examples/tilt/bench/mutexbench_moderate.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THREAD_COUNT 8 +#define BENCH_DURATION_SEC 10 + +#define IMPLICIT_INIT + +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +std::mt19937 prng_global{42}; // shared global PRNG +std::atomic done{false}; +std::atomic iterations_total{0}; + +void* worker(void* arg) { + uint64_t iterations_local = 0; + std::mt19937 prng_local{42}; // local PRNG + while (!atomic_load_explicit(&done, std::memory_order_relaxed)) { + pthread_mutex_lock(&lock); + // Critical section + prng_global(); // one step of PRNG in critical section + + pthread_mutex_unlock(&lock); + + // Non-critical section + // (sleep or dummy ops to simulate delay) + ++iterations_local; + } + atomic_fetch_add_explicit(&iterations_total, iterations_local, std::memory_order_relaxed); + return NULL; +} + +int main() { + #if !defined(IMPLICIT_INIT) + if (pthread_mutex_init(&lock, NULL) != 0) { + printf("Mutex initialization failed\n"); + return 1; + } + #endif /* EXPLICIT_INIT */ + + pthread_t threads[THREAD_COUNT]; + for (int i = 0; i < THREAD_COUNT; ++i) + pthread_create(&threads[i], NULL, worker, NULL); + + //sleep(BENCH_DURATION_SEC); + std::this_thread::sleep_for(std::chrono::seconds(BENCH_DURATION_SEC)); + atomic_store_explicit(&done, 1, std::memory_order_relaxed); + + + for (int i = 0; i < THREAD_COUNT; ++i) + pthread_join(threads[i], NULL); + + std::cout << "Total iterations: " << iterations_total.load() << "\n"; + pthread_mutex_destroy(&lock); + + return 0; +} diff --git a/examples/tilt/campaign_tilt.py b/examples/tilt/campaign_tilt.py index 849857cc..caf94b1b 100755 --- a/examples/tilt/campaign_tilt.py +++ b/examples/tilt/campaign_tilt.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 -# Copyright (C) 2024 Vrije Universiteit Brussel. All rights reserved. +# Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. # SPDX-License-Identifier: MIT """ Minimal example to run tilt locks. """ -from tiltlib import SimpleMutexTestBench as Bench +# from tiltlib import SimpleMutexTestBench as Bench from tiltlib import TiltLib +from test_bench import SimpleMutexTestBench as TestBench +from mutex_bench import MutexBench from benchkit.campaign import CampaignCartesianProduct, CampaignSuite from benchkit.utils.dir import caller_dir @@ -17,33 +19,150 @@ bench_src_dir = campaign_dir / "bench" vsync_dir = (tilt_locks_dir / "../deps/libvsync/").resolve() +NB_RUNS = 7 +LOCKS = [ + # "clhlock", + "hemlock", + "mcslock", + "reciplock_impl", + "ticketlock", + "twalock", +] + +BENCHMARK_DURATION_SECONDS = 10 + + +# def campaign_test(tilt_lib) -> CampaignCartesianProduct: +# benchmark_name = "mutex_test" +# return CampaignCartesianProduct( +# name=f"tilt_{benchmark_name}", +# benchmark=TestBench( +# src_dir=bench_src_dir, +# shared_libs=[tilt_lib], +# ), +# nb_runs=1, +# variables={ +# "lock": LOCKS + ["taslock", "caslock", "vcaslock-nolse", "vcaslock-lse"], +# }, +# constants={ +# "benchmark_duration_seconds": BENCHMARK_DURATION_SECONDS, +# "benchmark_name": benchmark_name, +# }, +# debug=False, +# gdb=False, +# enable_data_dir=True, +# continuing=False, +# ) + +# def campaign_mutex_bench(tilt_lib) -> CampaignCartesianProduct: +# benchmark_name = "mutex_bench" +# CampaignCartesianProduct( +# name=f"tilt_{benchmark_name}", +# benchmark=MutexBench( +# src_dir=bench_src_dir, +# shared_libs=[tilt_lib], +# ), +# nb_runs=1, +# variables={ +# "lock": LOCKS, +# # "nb_threads": THREADS, +# }, +# constants={ +# "benchmark_name": benchmark_name, +# "benchmark_duration_seconds": BENCHMARK_DURATION_SECONDS, +# }, +# debug=False, +# gdb=False, +# enable_data_dir=True, +# continuing=False, +# benchmark_duration_seconds=BENCHMARK_DURATION_SECONDS, +# pretty={ +# "lock": { +# # "clhlock": "CLH lock", +# "hemlock": "Hemlock", +# "mcslock": "MCS lock", +# "reciplock_impl": "Reciprocating lock", +# "ticketlock": "Ticketlock", +# "twalock": "TWA lock", +# }, +# }, +# ) def main() -> None: - tiltlib = TiltLib(tilt_locks_dir=tilt_locks_dir) - tiltlib.build() - - campaigns = [ - CampaignCartesianProduct( - name="tilt", - benchmark=Bench( - src_dir=bench_src_dir, - shared_libs=[tiltlib], - ), - nb_runs=1, - variables={ - "lock": ["", "taslock", "caslock", "vcaslock-nolse", "vcaslock-lse"], - }, - constants=None, - debug=False, - gdb=False, - enable_data_dir=True, - continuing=False, - ), - ] - suite = CampaignSuite(campaigns=campaigns) - suite.print_durations() - suite.run_suite() + tiltlib = TiltLib(tilt_locks_dir=tilt_locks_dir) + tiltlib.build() + + benchmark_name = "mutex_bench" + campaign_mutex = CampaignCartesianProduct( + name="tilt", + benchmark=MutexBench( + src_dir=bench_src_dir, + shared_libs=[tiltlib], + ), + nb_runs=NB_RUNS, + variables={ + "lock": LOCKS, + "nb_threads": range(1, 5), # 1-4 threads + }, + constants={ + "benchmark_name": benchmark_name, + "benchmark_duration_seconds": BENCHMARK_DURATION_SECONDS, + }, + debug=False, + gdb=False, + enable_data_dir=True, + continuing=False, + benchmark_duration_seconds=BENCHMARK_DURATION_SECONDS, + pretty={ + "lock": { + # "clhlock": "CLH lock", + "hemlock": "Hemlock", + "mcslock": "MCS lock", + "reciplock_impl": "Reciprocating lock", + "ticketlock": "Ticketlock", + "twalock": "TWA lock", + }, + }, + ) + + benchmark_name = "mutex_test" + campaign_test = CampaignCartesianProduct( + name="tilt", + benchmark=TestBench( + src_dir=bench_src_dir, + shared_libs=[tiltlib], + ), + nb_runs=1, + variables={ + "lock": LOCKS + ["taslock", "caslock", "vcaslock-nolse", "vcaslock-lse"], + }, + constants={ + "benchmark_name": benchmark_name, + }, + debug=False, + gdb=False, + enable_data_dir=True, + continuing=False, + ) + + campaigns = [ + # campaign_test(tiltlib), + # campaign_mutex_bench(tiltlib), + # campaign_test, + campaign_mutex, + ] + + suite = CampaignSuite(campaigns=campaigns) + suite.print_durations() + suite.run_suite() + + suite.generate_graph( + plot_name="lineplot", + x="nb_threads", + y="total_iterations", + hue="lock", + ) if __name__ == "__main__": - main() + main() diff --git a/examples/tilt/deps/reciplock/CMakeLists.txt b/examples/tilt/deps/reciplock/CMakeLists.txt new file mode 100644 index 00000000..402d6ea6 --- /dev/null +++ b/examples/tilt/deps/reciplock/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.10) +project( + reciplock + LANGUAGES C + VERSION 2.1.0 +) + +include_directories(include) +add_library(reciplock INTERFACE) +target_include_directories(reciplock INTERFACE include) diff --git a/examples/tilt/deps/reciplock/include/reciplock.h b/examples/tilt/deps/reciplock/include/reciplock.h new file mode 100644 index 00000000..f2f684fb --- /dev/null +++ b/examples/tilt/deps/reciplock/include/reciplock.h @@ -0,0 +1,248 @@ +// +// Created by niels on 30/05/2025. +// + +#ifndef RECIP_RECIPLOCK_H +#define RECIP_RECIPLOCK_H +/******************************************************************************* + * @file reciplock.h + * @brief one sentence that describes the algorithm. + * + * @ingroup lock_free // if your algo belongs to certain groups you + * // can list them here separated with spaces + * + * + * // add detailed information about the algorithm + * // operating conditions etc. + * + * + * @example + * @include eg_reciplock.c + * * + * @cite + * David Dice, Alex Kogan - [Reciprocating Locks. arXiv:2501.02380] + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#define TLS_IMPLEMENTATION 1 + +// a thread knows only the identity of its immediate neighbor in the arrival segment +typedef struct reciplock_node_s { + _Atomic(struct reciplock_node_s *) next; // next node in waiting +} reciplock_node_t; + +/** Reciprocating lock data structure. */ +typedef struct reciplock_s { + _Atomic(reciplock_node_t *) tail; // tail of the arrival segment +} reciplock_t; + +// Spinner, if not nullptr then we can enter critical section +static __thread reciplock_node_t tls_wait_node; // thread-specific waiting element + +#if TLS_IMPLEMENTATION +static __thread reciplock_node_t *tls_successor; +static __thread reciplock_node_t *tls_end_of_segment; +#endif + +// encoding of the "simple-locked" state, lock is active but nothing in entry segment +static reciplock_node_t * const LOCKED_EMPTY = (reciplock_node_t *)(uintptr_t) 1; + +/** Initializer of `reciplock_t`. */ +#define RECIPLOCK_INIT() \ +{ \ + .tail = ATOMIC_VAR_INIT(NULL) \ +} + +/** + * Initializes the reciprocating lock. + * + * @param lock address of reciplock_t object. + * + * @note alternatively use `RECIPLOCK_INIT`. + */ +static inline void +reciplock_init(reciplock_t *lock) +{ + atomic_store_explicit( &lock->tail, NULL, memory_order_release ); +} + +// segment_end and succ reflect context to be passed from Acquire to corresponding Release +// Could also pass the context via fields in the lock body or into TLS. + +/** + * Acquires the reciprocating lock. + * + * @param lock address of reciplock_t object. + * @param _segment_end address of reciplock_node_t object, associated with the calling + * thread/core. + * @return Pointer to successor node if one exists; NULL otherwise. + * + * Each thread uses its own tls_wait_node for participation. + * @remarks segment_end and successor node are used to pass context from Acquire to Release. + * Could also pass the context via fields in the lock body or into TLS. + */ +static inline reciplock_node_t * +#if TLS_IMPLEMENTATION +reciplock_acquire( reciplock_t *lock ) +#else +reciplock_acquire( reciplock_t *lock, reciplock_node_t **_segment_end ) +#endif +{ + // initializes its thread-specific waiting element in anticipation of potential contention + atomic_store_explicit( &tls_wait_node.next, NULL, memory_order_release ); + + reciplock_node_t *succ = NULL; + reciplock_node_t *segment_end = &tls_wait_node; // currently nullptr + + // swaps the address of wait_element into lock's arrival word; + reciplock_node_t *const tail_prev = atomic_exchange( &lock->tail, &tls_wait_node ); + assert( tail_prev != &tls_wait_node ); + + // Lock is held + if ( tail_prev != NULL ) { + // coerce LOCKED_EMPTY to null + // succ will be our successor when we subsequently release + succ = (reciplock_node_t *)(((uintptr_t) tail_prev) & ~1); + assert(succ != &tls_wait_node); + + // Spin until our predecessor grants us access. + for ( ;; ) { + segment_end = atomic_load_explicit( &tls_wait_node.next, memory_order_acquire ); + if ( segment_end != NULL ) break; + //Pause(); + } + assert( segment_end != &tls_wait_node ); + + // Detect logical end-of-segment terminus address + if ( succ == segment_end ) { + succ = NULL; // quash + segment_end = LOCKED_EMPTY; + } + } +#if TLS_IMPLEMENTATION + tls_successor = succ; + tls_end_of_segment = segment_end; +#else + *_segment_end = segment_end; +#endif + return succ; +} + +#if TLS_IMPLEMENTATION +/** + * Releases the reciprocating lock. + * + * @param lock address of reciplock_t object. + */ +static inline void +reciplock_release( reciplock_t *lock ) { + assert( tls_end_of_segment != NULL ); + assert( atomic_load_explicit(&lock->tail, memory_order_acquire) != NULL ); + + if ( tls_successor != NULL ) { + assert( atomic_load(&tls_successor->next) == NULL ); + + // Notify successor that it may proceed. + atomic_store_explicit( &tls_successor->next, tls_end_of_segment, memory_order_release ); + return; + } + + assert( tls_end_of_segment == LOCKED_EMPTY || tls_end_of_segment == &tls_wait_node ); +#if 0 + reciplock_node_t * v = tls_end_of_segment; + + // empty entry segment + if ( atomic_compare_exchange_strong_explicit( &lock->tail, &v, NULL, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) { + // uncontended fast-path return + return; + } +#else + // Fast path: if the tail is empty, we can just set it to NULL + if ( atomic_load_explicit( &lock->tail, memory_order_acquire ) == tls_end_of_segment ) { + reciplock_node_t *v = tls_end_of_segment; + if ( atomic_compare_exchange_strong_explicit( &lock->tail, &v, NULL, memory_order_seq_cst, memory_order_seq_cst ) ) { + // uncontended fast-path return + return; + } + } +#endif + + // Slow path: tail is not empty, we need to detach the arrival segment + reciplock_node_t *w = atomic_exchange( &lock->tail, LOCKED_EMPTY ); // detach arrival segment + assert( w != NULL ); + assert( w != LOCKED_EMPTY ); + assert( w != &tls_wait_node ); + assert( atomic_load_explicit( &w->next, memory_order_acquire ) == NULL ); + + // Let the new arrival know the segment's boundary + atomic_store_explicit( &w->next, tls_end_of_segment, memory_order_release ); // pass ownership; pass address of wait_element as end of segment marker +} +#else +/** + * Releases the reciprocating lock. + * + * @param lock address of reciplock_t object. + * @param end_of_segment address of reciplock_node_t object, associated with the calling + * thread/core. + * @param succ address of successor node (if one exists), to which control is handed off. + * @remarks segment_end and successor node are used to pass context from Acquire to Release. + * Could also pass the context via fields in the lock body or into TLS. + */ +static inline void +reciplock_release( reciplock_t *lock, reciplock_node_t *end_of_segment, reciplock_node_t *succ ) { + assert( end_of_segment != NULL ); + assert( atomic_load_explicit(&lock->tail, memory_order_acquire) != NULL ); + + if ( succ != NULL ) { + assert( atomic_load(&succ->next) == NULL ); + + // Notify successor that it may proceed. + atomic_store_explicit( &succ->next, end_of_segment, memory_order_release ); + return; + } + + assert( end_of_segment == LOCKED_EMPTY || end_of_segment == &tls_wait_node ); +#if 0 + reciplock_node_t * v = end_of_segment; + + // empty entry segment + if ( atomic_compare_exchange_strong_explicit( &lock->tail, &v, NULL, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) { + // uncontended fast-path return + return; + } +#else + // Fast path: if the tail is empty, we can just set it to NULL + if ( atomic_load_explicit( &lock->tail, memory_order_acquire ) == end_of_segment ) { + reciplock_node_t *v = end_of_segment; + if ( atomic_compare_exchange_strong_explicit( &lock->tail, &v, NULL, memory_order_seq_cst, memory_order_seq_cst ) ) { + // uncontended fast-path return + return; + } + } +#endif + + // Slow path: tail is not empty, we need to detach the arrival segment + reciplock_node_t *w = atomic_exchange( &lock->tail, LOCKED_EMPTY ); // detach arrival segment + assert( w != NULL ); + assert( w != LOCKED_EMPTY ); + assert( w != &tls_wait_node ); + assert( atomic_load_explicit( &w->next, memory_order_acquire ) == NULL ); + + // Let the new arrival know the segment's boundary + atomic_store_explicit( &w->next, end_of_segment, memory_order_release ); // pass ownership; pass address of wait_element as end of segment marker +} +#endif + +//void __attribute__((noinline)) ctor() { +// atomic_store_explicit( &lock.arrivals, NULL, memory_order_release ); +//} // ctor +// +//void __attribute__((noinline)) dtor() { +//} // dtor +#endif //RECIP_RECIPLOCK_H diff --git a/examples/tilt/kit/mutex_bench.py b/examples/tilt/kit/mutex_bench.py new file mode 100644 index 00000000..20739107 --- /dev/null +++ b/examples/tilt/kit/mutex_bench.py @@ -0,0 +1,152 @@ +# Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +# SPDX-License-Identifier: MIT + +import pathlib +from typing import Any, Dict, Iterable, List + +from benchkit.benchmark import ( + Benchmark, + CommandAttachment, + CommandWrapper, + PostRunHook, + PreRunHook, + SharedLib, +) +from benchkit.platforms import Platform +from benchkit.utils.dir import caller_dir +from benchkit.utils.types import PathType + +from tiltlib import cmake_configure_build + +class MutexBench(Benchmark): + def __init__( + self, + src_dir: PathType = "", + command_wrappers: Iterable[CommandWrapper] = (), + command_attachments: Iterable[CommandAttachment] = (), + shared_libs: Iterable[SharedLib] = (), + pre_run_hooks: Iterable[PreRunHook] = (), + post_run_hooks: Iterable[PostRunHook] = (), + platform: Platform = None, + ) -> None: + super().__init__( + command_wrappers=command_wrappers, + command_attachments=command_attachments, + shared_libs=shared_libs, + pre_run_hooks=pre_run_hooks, + post_run_hooks=post_run_hooks, + ) + if platform is not None: + self.platform = platform + if src_dir: + self._src_dir = pathlib.Path(src_dir).resolve() + else: + self._src_dir = (caller_dir() / "../bench").resolve() + self._build_dir = self._src_dir / f"build-{self.platform.hostname}" + + @property + def bench_src_path(self) -> pathlib.Path: + return self._src_dir + + @staticmethod + def get_build_var_names() -> List[str]: + return [ + # "benchmark_duration_seconds", + # "lock", + "nb_threads", + ] + + @staticmethod + def get_run_var_names() -> List[str]: + return [ + # "benchmark_name", + # "lock", + ] + + def clean_bench(self) -> None: + pass + + # def prebuild_bench( + # self, + # **kwargs, + # ) -> int: + + # print("[INFO] Building bench...") + # cmake_configure_build( + # platform=self.platform, + # src_dir=self.bench_src_path, + # build_dir=self._build_dir, + # debug=self.must_debug(), + # make_suffix=self._parallel_make_str(), + # ) + # return 0 + + def prebuild_bench( + self, + **kwargs, + ) -> None: + pass + + def build_bench( + self, + nb_threads: int, + **kwargs, + ) -> None: + # pass + + print("[INFO] Building bench...") + duration = self._constants.get("benchmark_duration_seconds") + + command_args = [ + f"-DRUN_DURATION_SECONDS={duration}", + f"-DNB_THREADS={nb_threads}", + ] + cmake_configure_build( + platform=self.platform, + src_dir=self.bench_src_path, + build_dir=self._build_dir, + debug=self.must_debug(), + make_suffix=self._parallel_make_str(), + arg_list=command_args, + ) + return 0 + + def single_run( + self, + **kwargs, + ) -> str: + current_dir = self._build_dir + environment = self._preload_env( + **kwargs, + ) + print(f"[INFO] Running bench in {current_dir} with environment {environment}") + benchmark_name = self._constants.get("benchmark_name") + run_command = [f"./{benchmark_name}"] + + wrapped_run_command, wrapped_environment = self._wrap_command( + run_command=run_command, + environment=environment, + **kwargs, + ) + + output = self.run_bench_command( + run_command=run_command, + wrapped_run_command=wrapped_run_command, + current_dir=current_dir, + environment=environment, + wrapped_environment=wrapped_environment, + print_output=False, + ignore_ret_codes=(1,), + ) + print(output) + return output + + def parse_output_to_results( # pylint: disable=arguments-differ + self, + command_output: str, + run_variables: Dict[str, Any], + **_kwargs, + ) -> Dict[str, Any]: + key_seq_values = command_output.strip().split(";") + result_dict = dict(map(lambda s: s.split("="), key_seq_values)) + return result_dict diff --git a/examples/tilt/kit/test_bench.py b/examples/tilt/kit/test_bench.py new file mode 100644 index 00000000..13bafd8a --- /dev/null +++ b/examples/tilt/kit/test_bench.py @@ -0,0 +1,138 @@ +# Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +# SPDX-License-Identifier: MIT + +import pathlib +from typing import Any, Dict, Iterable, List + +from benchkit.benchmark import ( + Benchmark, + CommandAttachment, + CommandWrapper, + PostRunHook, + PreRunHook, + SharedLib, +) +from benchkit.platforms import Platform +from benchkit.utils.dir import caller_dir +from benchkit.utils.types import PathType + +from tiltlib import cmake_configure_build + +class SimpleMutexTestBench(Benchmark): + """Benchmark object for testing correctness of our lock implementations for simple mutex operations.""" + def __init__( + self, + src_dir: PathType = "", + command_wrappers: Iterable[CommandWrapper] = (), + command_attachments: Iterable[CommandAttachment] = (), + shared_libs: Iterable[SharedLib] = (), + pre_run_hooks: Iterable[PreRunHook] = (), + post_run_hooks: Iterable[PostRunHook] = (), + platform: Platform = None, + ) -> None: + super().__init__( + command_wrappers=command_wrappers, + command_attachments=command_attachments, + shared_libs=shared_libs, + pre_run_hooks=pre_run_hooks, + post_run_hooks=post_run_hooks, + ) + if platform is not None: + self.platform = platform + if src_dir: + self._src_dir = pathlib.Path(src_dir).resolve() + else: + self._src_dir = (caller_dir() / "../bench").resolve() + self._build_dir = self._src_dir / f"build-{self.platform.hostname}" + + @property + def bench_src_path(self) -> pathlib.Path: + return self._src_dir + + @staticmethod + def get_build_var_names() -> List[str]: + return [ + # "benchmark_duration_seconds", + # "lock", + ] + + @staticmethod + def get_run_var_names() -> List[str]: + return [ + # "benchmark_name", + # "lock", + ] + + def clean_bench(self) -> None: + pass + + # def prebuild_bench( + # self, + # **kwargs, + # ) -> int: + + # print("[INFO] Building bench...") + # cmake_configure_build( + # platform=self.platform, + # src_dir=self.bench_src_path, + # build_dir=self._build_dir, + # debug=self.must_debug(), + # make_suffix=self._parallel_make_str(), + # ) + # return 0 + + def prebuild_bench( + self, + **kwargs, + ) -> None: + pass + + def build_bench( + self, + **kwargs, + ) -> int: + cmake_configure_build( + platform=self.platform, + src_dir=self.bench_src_path, + build_dir=self._build_dir, + debug=self.must_debug(), + make_suffix=self._parallel_make_str(), + ) + return 0 + + def single_run( + self, + **kwargs, + ) -> str: + current_dir = self._build_dir + environment = self._preload_env( + **kwargs, + ) + benchmark_name = self._constants.get("benchmark_name") + run_command = [f"./{benchmark_name}"] + + wrapped_run_command, wrapped_environment = self._wrap_command( + run_command=run_command, + environment=environment, + **kwargs, + ) + + output = self.run_bench_command( + run_command=run_command, + wrapped_run_command=wrapped_run_command, + current_dir=current_dir, + environment=environment, + wrapped_environment=wrapped_environment, + print_output=False, + ignore_ret_codes=(1,), + ) + print(output) + return output + + def parse_output_to_results( # pylint: disable=arguments-differ + self, + command_output: str, + **_kwargs, + ) -> Dict[str, Any]: + result_dict = {} + return result_dict diff --git a/examples/tilt/kit/tiltlib.py b/examples/tilt/kit/tiltlib.py index cfcb6981..83daf929 100644 --- a/examples/tilt/kit/tiltlib.py +++ b/examples/tilt/kit/tiltlib.py @@ -1,187 +1,88 @@ -# Copyright (C) 2024 Vrije Universiteit Brussel. All rights reserved. +# Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. # SPDX-License-Identifier: MIT import pathlib import sys -from typing import Any, Dict, Iterable, List, Tuple +from typing import Tuple -from benchkit.benchmark import ( - Benchmark, - CommandAttachment, - CommandWrapper, - PostRunHook, - PreRunHook, - SharedLib, -) from benchkit.platforms import Platform from benchkit.sharedlibs import FromSourceSharedLib -from benchkit.utils.dir import caller_dir -from benchkit.utils.types import EnvironmentVariables, LdPreloadLibraries, PathType +from benchkit.utils.types import EnvironmentVariables, LdPreloadLibraries def cmake_configure_build( - platform: Platform, - src_dir: pathlib.Path, - build_dir: pathlib.Path, - debug: bool, - make_suffix: str, + platform: Platform, + src_dir: pathlib.Path, + build_dir: pathlib.Path, + debug: bool, + make_suffix: str, + arg_list: list[str] = [], ) -> None: - platform.comm.makedirs(path=build_dir, exist_ok=True) - - cmake_build_type = "Debug" if debug else "Release" - platform.comm.shell( - command=["cmake", f"-DCMAKE_BUILD_TYPE={cmake_build_type}", f"{src_dir}"], - current_dir=build_dir, - output_is_log=True, - ) - platform.comm.shell( - command=f"make{make_suffix}", - current_dir=build_dir, - output_is_log=True, - ) + platform.comm.makedirs(path=build_dir, exist_ok=True) + + cmake_build_type = "Debug" if debug else "Release" + command = [ + "cmake", + f"-DCMAKE_BUILD_TYPE={cmake_build_type}", + f"{src_dir}" + ] + + platform.comm.shell( + command=command + arg_list, + current_dir=build_dir, + output_is_log=True, + ) + platform.comm.shell( + command=f"make{make_suffix}", + current_dir=build_dir, + output_is_log=True, + ) class TiltLib(FromSourceSharedLib): - def __init__( - self, - tilt_locks_dir: pathlib.Path, - build_prefix: str = "build", - debug_mode: bool = False, - ) -> None: - super().__init__(src_path=tilt_locks_dir, debug_mode=debug_mode) - self.build_dir = self.src_path / f"{build_prefix}-{self.platform.hostname}" - - def build(self) -> None: - cmake_configure_build( - platform=self.platform, - src_dir=self.src_path, - build_dir=self.build_dir, - debug=self._debug_mode, - make_suffix=" -j1", - ) - - def preload( - self, - **kwargs, - ) -> Tuple[LdPreloadLibraries, EnvironmentVariables]: - ld_preloads, env_vars = super().preload( - **kwargs, - ) - - lock = kwargs.get("lock") - if lock: - lib_lockname = f"lib{lock}" - lib_path = (self.build_dir / f"{lib_lockname}.so").resolve() - if lib_path.is_file(): - ld_preloads.append(str(lib_path)) - else: - print( - ( - f"[WARNING] Tilt lib lock with name {lib_lockname} not found " - f"(in '{lib_path}'), not enabling." - ), - file=sys.stderr, - ) - raise ValueError( - f"Tilt lib lock with name {lib_lockname} not found in '{lib_path}'" - ) - - return ld_preloads, env_vars - - -class SimpleMutexTestBench(Benchmark): - def __init__( - self, - src_dir: PathType = "", - command_wrappers: Iterable[CommandWrapper] = (), - command_attachments: Iterable[CommandAttachment] = (), - shared_libs: Iterable[SharedLib] = (), - pre_run_hooks: Iterable[PreRunHook] = (), - post_run_hooks: Iterable[PostRunHook] = (), - platform: Platform = None, - ) -> None: - super().__init__( - command_wrappers=command_wrappers, - command_attachments=command_attachments, - shared_libs=shared_libs, - pre_run_hooks=pre_run_hooks, - post_run_hooks=post_run_hooks, - ) - if platform is not None: - self.platform = platform - if src_dir: - self._src_dir = pathlib.Path(src_dir).resolve() - else: - self._src_dir = (caller_dir() / "../bench").resolve() - self._build_dir = self._src_dir / f"build-{self.platform.hostname}" - - @property - def bench_src_path(self) -> pathlib.Path: - return self._src_dir - - @staticmethod - def get_build_var_names() -> List[str]: - return [] - - @staticmethod - def get_run_var_names() -> List[str]: - return ["lock"] - - def clean_bench(self) -> None: - pass - - def prebuild_bench( - self, - **kwargs, - ) -> int: - cmake_configure_build( - platform=self.platform, - src_dir=self.bench_src_path, - build_dir=self._build_dir, - debug=self.must_debug(), - make_suffix=self._parallel_make_str(), - ) - return 0 - - def build_bench( - self, - **kwargs, - ) -> None: - pass - - def single_run( - self, - **kwargs, - ) -> str: - current_dir = self._build_dir - environment = self._preload_env( - **kwargs, - ) - - run_command = ["./mutex"] - - wrapped_run_command, wrapped_environment = self._wrap_command( - run_command=run_command, - environment=environment, - **kwargs, - ) - - output = self.run_bench_command( - run_command=run_command, - wrapped_run_command=wrapped_run_command, - current_dir=current_dir, - environment=environment, - wrapped_environment=wrapped_environment, - print_output=False, - ignore_ret_codes=(1,), - ) - print(output) - return output - - def parse_output_to_results( # pylint: disable=arguments-differ - self, - command_output: str, - **_kwargs, - ) -> Dict[str, Any]: - result_dict = {} - return result_dict + def __init__( + self, + tilt_locks_dir: pathlib.Path, + build_prefix: str = "build", + debug_mode: bool = False, + ) -> None: + super().__init__(src_path=tilt_locks_dir, debug_mode=debug_mode) + self.build_dir = self.src_path / f"{build_prefix}-{self.platform.hostname}" + + def build(self) -> None: + print("[INFO] Building Tilt library...") + cmake_configure_build( + platform=self.platform, + src_dir=self.src_path, + build_dir=self.build_dir, + debug=self._debug_mode, + make_suffix=" -j1", + ) + + def preload( + self, + **kwargs, + ) -> Tuple[LdPreloadLibraries, EnvironmentVariables]: + ld_preloads, env_vars = super().preload( + **kwargs, + ) + + lock = kwargs.get("lock") + if lock: + lib_lockname = f"lib{lock}" + lib_path = (self.build_dir / f"{lib_lockname}.so").resolve() + if lib_path.is_file(): + ld_preloads.append(str(lib_path)) + else: + print( + ( + f"[WARNING] Tilt lib lock with name {lib_lockname} not found " + f"(in '{lib_path}'), not enabling." + ), + file=sys.stderr, + ) + raise ValueError( + f"Tilt lib lock with name {lib_lockname} not found in '{lib_path}'" + ) + + return ld_preloads, env_vars diff --git a/examples/tilt/locks/CMakeLists.txt b/examples/tilt/locks/CMakeLists.txt index f37392bb..0cc2fe8d 100644 --- a/examples/tilt/locks/CMakeLists.txt +++ b/examples/tilt/locks/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(tiltlock C) add_subdirectory(${CMAKE_SOURCE_DIR}/../deps/libvsync/ build_libvsync) +add_subdirectory(${CMAKE_SOURCE_DIR}/../deps/reciplock/ build_reciplock) include_directories(include ${CMAKE_SOURCE_DIR}/../deps/tilt/include/) @@ -10,9 +11,21 @@ add_library(taslock SHARED taslock.c) add_library(caslock SHARED caslock.c) add_library(vcaslock-lse SHARED vcaslock.c) add_library(vcaslock-nolse SHARED vcaslock.c) +add_library(clhlock SHARED clhlock.c) +add_library(hemlock SHARED hemlock.c) +add_library(mcslock SHARED mcslock.c) +add_library(ticketlock SHARED ticketlock.c) +add_library(twalock SHARED twalock.c) +add_library(reciplock_impl SHARED reciplock.c) target_link_libraries(vcaslock-lse vsync) target_link_libraries(vcaslock-nolse vsync) +target_link_libraries(clhlock vsync) +target_link_libraries(hemlock vsync) +target_link_libraries(mcslock vsync) +target_link_libraries(ticketlock vsync) +target_link_libraries(twalock vsync) +target_link_libraries(reciplock_impl reciplock) if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "armv8") target_compile_definitions(vcaslock-nolse PRIVATE VATOMIC_DISABLE_ARM64_LSE) diff --git a/examples/tilt/locks/clhlock.c b/examples/tilt/locks/clhlock.c new file mode 100644 index 00000000..b2c09929 --- /dev/null +++ b/examples/tilt/locks/clhlock.c @@ -0,0 +1,37 @@ +#include +#include + +static __thread clh_node_t context; + +typedef struct tilt_mutex { + clhlock_t lock; +} tilt_mutex_t; + +static void +tilt_mutex_init(tilt_mutex_t *m) +{ + clhlock_init(&m->lock); +} + +static void +tilt_mutex_destroy(tilt_mutex_t *m) +{ +} + +static void +tilt_mutex_lock(tilt_mutex_t *m) +{ + clhlock_acquire(&m->lock, &context); +} + +static void +tilt_mutex_unlock(tilt_mutex_t *m) +{ + clhlock_release(&m->lock, &context); +} + +static bool +tilt_mutex_trylock(tilt_mutex_t *m) +{ +} + diff --git a/examples/tilt/locks/hemlock.c b/examples/tilt/locks/hemlock.c new file mode 100644 index 00000000..0ded4ab3 --- /dev/null +++ b/examples/tilt/locks/hemlock.c @@ -0,0 +1,38 @@ +#include +#include + +static __thread hem_node_t context; + +typedef struct tilt_mutex { + hemlock_t lock; +} tilt_mutex_t; + +static void +tilt_mutex_init(tilt_mutex_t *m) +{ + hemlock_init(&m->lock); +} + +static void +tilt_mutex_destroy(tilt_mutex_t *m) +{ +} + +static void +tilt_mutex_lock(tilt_mutex_t *m) +{ + hemlock_acquire(&m->lock, &context); +} + +static void +tilt_mutex_unlock(tilt_mutex_t *m) +{ + hemlock_release(&m->lock, &context); +} + +static bool +tilt_mutex_trylock(tilt_mutex_t *m) +{ + return hemlock_tryacquire(&m->lock, &context); +} + diff --git a/examples/tilt/locks/mcslock.c b/examples/tilt/locks/mcslock.c new file mode 100644 index 00000000..37137fe7 --- /dev/null +++ b/examples/tilt/locks/mcslock.c @@ -0,0 +1,37 @@ +#include +#include + +static __thread mcs_node_t context; + +typedef struct tilt_mutex { + mcslock_t lock; +} tilt_mutex_t; + +static void +tilt_mutex_init(tilt_mutex_t *m) +{ + mcslock_init(&m->lock); +} + +static void +tilt_mutex_destroy(tilt_mutex_t *m) +{ +} + +static void +tilt_mutex_lock(tilt_mutex_t *m) +{ + mcslock_acquire(&m->lock, &context); +} + +static void +tilt_mutex_unlock(tilt_mutex_t *m) +{ + mcslock_release(&m->lock, &context); +} + +static bool +tilt_mutex_trylock(tilt_mutex_t *m) +{ +} + diff --git a/examples/tilt/locks/reciplock.c b/examples/tilt/locks/reciplock.c new file mode 100644 index 00000000..31cbee80 --- /dev/null +++ b/examples/tilt/locks/reciplock.c @@ -0,0 +1,32 @@ +#include +#include + +typedef struct tilt_mutex { + reciplock_t lock; +} tilt_mutex_t; + +static void tilt_mutex_init(tilt_mutex_t *m) +{ + reciplock_init(&m->lock); +} + +static void tilt_mutex_destroy(tilt_mutex_t *m) +{ + +} + +static void tilt_mutex_lock(tilt_mutex_t *m) +{ + reciplock_acquire(&m->lock); +} + +static void +tilt_mutex_unlock(tilt_mutex_t *m) +{ + reciplock_release(&m->lock); +} + +static bool +tilt_mutex_trylock(tilt_mutex_t *m) +{ +} diff --git a/examples/tilt/locks/ticketlock.c b/examples/tilt/locks/ticketlock.c new file mode 100644 index 00000000..370ce6bf --- /dev/null +++ b/examples/tilt/locks/ticketlock.c @@ -0,0 +1,32 @@ +#include +#include + +typedef struct tilt_mutex { + ticketlock_t lock; +} tilt_mutex_t; + +static void tilt_mutex_init(tilt_mutex_t *m) +{ + ticketlock_init(&m->lock); +} + +static void tilt_mutex_destroy(tilt_mutex_t *m) +{ +} + +static void tilt_mutex_lock(tilt_mutex_t *m) +{ + ticketlock_acquire(&m->lock); +} + +static void +tilt_mutex_unlock(tilt_mutex_t *m) +{ + ticketlock_release(&m->lock); +} + +static bool +tilt_mutex_trylock(tilt_mutex_t *m) +{ + return ticketlock_tryacquire(&m->lock); +} diff --git a/examples/tilt/locks/twalock.c b/examples/tilt/locks/twalock.c new file mode 100644 index 00000000..73db8483 --- /dev/null +++ b/examples/tilt/locks/twalock.c @@ -0,0 +1,35 @@ +#include +#include + +TWALOCK_ARRAY_DECL; +twalock_t g_lock = TWALOCK_INIT(); + +typedef struct tilt_mutex { + twalock_t lock; +} tilt_mutex_t; + +static void tilt_mutex_init(tilt_mutex_t *m) +{ + twalock_init(&m->lock); +} + +static void tilt_mutex_destroy(tilt_mutex_t *m) +{ +} + +static void tilt_mutex_lock(tilt_mutex_t *m) +{ + twalock_acquire(&m->lock); +} + +static void +tilt_mutex_unlock(tilt_mutex_t *m) +{ + twalock_release(&m->lock); +} + +static bool +tilt_mutex_trylock(tilt_mutex_t *m) +{ + return twalock_tryacquire(&m->lock); +} From 21d2452bb23961901d923aaa5a349595ff36a30e Mon Sep 17 00:00:00 2001 From: niels-vdp Date: Mon, 2 Jun 2025 01:53:12 +0100 Subject: [PATCH 2/7] examples/tilt: added benchmark & cleanup --- examples/tilt/.gitignore | 2 +- examples/tilt/bench/CMakeLists.txt | 18 +-- .../{atomic_bench.cpp => bench_atomic.cpp} | 36 ++++-- .../{mutex_bench.cpp => bench_mutex.cpp} | 0 examples/tilt/bench/bench_mutex_moderate.cpp | 93 ++++++++++++++ examples/tilt/bench/mutexbench_moderate.cpp | 64 ---------- .../tilt/bench/{mutex_test.c => test_mutex.c} | 0 examples/tilt/campaign_tilt.py | 114 +++++------------- examples/tilt/kit/mutex_bench.py | 1 + examples/tilt/kit/test_bench.py | 2 +- 10 files changed, 161 insertions(+), 169 deletions(-) rename examples/tilt/bench/{atomic_bench.cpp => bench_atomic.cpp} (59%) rename examples/tilt/bench/{mutex_bench.cpp => bench_mutex.cpp} (100%) create mode 100644 examples/tilt/bench/bench_mutex_moderate.cpp delete mode 100644 examples/tilt/bench/mutexbench_moderate.cpp rename examples/tilt/bench/{mutex_test.c => test_mutex.c} (100%) diff --git a/examples/tilt/.gitignore b/examples/tilt/.gitignore index 01eaf832..119b4dfc 100644 --- a/examples/tilt/.gitignore +++ b/examples/tilt/.gitignore @@ -1 +1 @@ -/*/build-*/* \ No newline at end of file +/*/build*/* \ No newline at end of file diff --git a/examples/tilt/bench/CMakeLists.txt b/examples/tilt/bench/CMakeLists.txt index bf8ecff3..38bdb329 100644 --- a/examples/tilt/bench/CMakeLists.txt +++ b/examples/tilt/bench/CMakeLists.txt @@ -2,23 +2,27 @@ # SPDX-License-Identifier: MIT cmake_minimum_required(VERSION 3.10) -project(PthreadMutexExample) +project(PthreadMutex) +# project(PthreadMutex LANGUAGES C CXX) # Build options set(NB_THREADS 4 CACHE STRING "Number of threads increasing the counter") set(RUN_DURATION_SECONDS 10 CACHE STRING "Time during which the threads will increase the counter") set(CMAKE_C_STANDARD 99) +# set(CMAKE_CXX_STANDARD 11) configure_file(${CMAKE_SOURCE_DIR}/include/config.h.in ${CMAKE_BINARY_DIR}/include/config.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) -add_executable(mutex_test mutex_test.c) -# add_executable(atomic_bench atomic_bench.cpp) -add_executable(mutex_bench mutex_bench.cpp) +add_executable(test_mutex test_mutex.c) +add_executable(bench_mutex bench_mutex.cpp) +add_executable(bench_mutex_moderate bench_mutex_moderate.cpp) +# add_executable(bench_atomic bench_atomic.cpp) find_package(Threads REQUIRED) -target_link_libraries(mutex_test Threads::Threads) -target_link_libraries(mutex_bench Threads::Threads) -# target_link_libraries(atomic_bench Threads::Threads) +target_link_libraries(test_mutex Threads::Threads) +target_link_libraries(bench_mutex Threads::Threads) +target_link_libraries(bench_mutex_moderate Threads::Threads) +# target_link_libraries(bench_atomic Threads::Threads) diff --git a/examples/tilt/bench/atomic_bench.cpp b/examples/tilt/bench/bench_atomic.cpp similarity index 59% rename from examples/tilt/bench/atomic_bench.cpp rename to examples/tilt/bench/bench_atomic.cpp index 36b07dbf..c1ed6aff 100644 --- a/examples/tilt/bench/atomic_bench.cpp +++ b/examples/tilt/bench/bench_atomic.cpp @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include #include @@ -7,9 +10,7 @@ #include #include #include - -#define THREAD_COUNT 8 -#define BENCH_DURATION_SEC 10 +#include /* defines NB_THREADS, RUN_DURATION_SECONDS */ #define IMPLICIT_INIT @@ -19,7 +20,6 @@ struct S { uint32_t a, b, c, d, e; }; std::atomic shared; - std::atomic done{false}; std::atomic iterations_total{0}; @@ -50,21 +50,35 @@ int main() { #endif /* EXPLICIT_INIT */ // Initialize shared atomic with zeros + S zeroes = {0, 0, 0, 0, 0}; + shared.store(zeroes, std::memory_order_relaxed); //atomic_store_explicit(&shared, {0, 0, 0, 0, 0}, std::memory_order_relaxed); - pthread_t threads[THREAD_COUNT]; - for (int i = 0; i < THREAD_COUNT; ++i) + pthread_t threads[NB_THREADS]; + unsigned long thread_iterations[NB_THREADS]; + for (int i = 0; i < NB_THREADS; ++i) pthread_create(&threads[i], NULL, worker, NULL); - //sleep(BENCH_DURATION_SEC); - std::this_thread::sleep_for(std::chrono::seconds(BENCH_DURATION_SEC)); + // Run for a fixed duration + std::this_thread::sleep_for(std::chrono::seconds(RUN_DURATION_SECONDS)); atomic_store_explicit(&done, 1, std::memory_order_relaxed); + for (int i = 0; i < NB_THREADS; ++i) { + void* return_value; + pthread_join(threads[i], &return_value); + thread_iterations[i] = (unsigned long) return_value; + } + + std::cout << "total_iterations=" << iterations_total.load(); + std::cout << ";duration=" << RUN_DURATION_SECONDS; + std::cout << ";nb_threads=" << NB_THREADS; - for (int i = 0; i < THREAD_COUNT; ++i) - pthread_join(threads[i], NULL); + // print per thread iterations + for (size_t k = 0u; k < NB_THREADS; ++k) { + std::cout << ";thread_" << k << "=" << thread_iterations[k]; + } - std::cout << "Total iterations: " << iterations_total.load() << "\n"; + std::cout << "\n"; pthread_mutex_destroy(&lock); return 0; diff --git a/examples/tilt/bench/mutex_bench.cpp b/examples/tilt/bench/bench_mutex.cpp similarity index 100% rename from examples/tilt/bench/mutex_bench.cpp rename to examples/tilt/bench/bench_mutex.cpp diff --git a/examples/tilt/bench/bench_mutex_moderate.cpp b/examples/tilt/bench/bench_mutex_moderate.cpp new file mode 100644 index 00000000..02f615be --- /dev/null +++ b/examples/tilt/bench/bench_mutex_moderate.cpp @@ -0,0 +1,93 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* defines NB_THREADS, RUN_DURATION_SECONDS */ + +#define IMPLICIT_INIT + +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +std::mt19937 prng_global{42}; // shared global PRNG +std::atomic done{false}; +std::atomic iterations_total{0}; + +volatile uint32_t prng_sink_global = 0; // Necessary to prevent optimization of the non-critical section + +void* worker(void* arg) { + uint64_t iterations_local = 0; + + // Setup local PRNG for non-critical section + std::mt19937 prng_local{42}; // local PRNG + std::uniform_int_distribution dist(0, 249); + uint32_t prng_sink = 0; // value to prevent optimization + + while (!atomic_load_explicit(&done, std::memory_order_relaxed)) { + + // Critical section + pthread_mutex_lock(&lock); + prng_global(); // one step of PRNG in critical section + pthread_mutex_unlock(&lock); + + // Non-critical section + int steps = dist(prng_local); + for (int i = 0; i < steps; ++i) + prng_sink += prng_local(); + + ++iterations_local; + } + + // Consume final value so optimizer can't skip + prng_sink_global += prng_sink; + + atomic_fetch_add_explicit(&iterations_total, iterations_local, std::memory_order_relaxed); + + void *result = (void*) iterations_local; + return result; +} + +int main() { + #if !defined(IMPLICIT_INIT) + if (pthread_mutex_init(&lock, NULL) != 0) { + printf("Mutex initialization failed\n"); + return 1; + } + #endif /* EXPLICIT_INIT */ + + pthread_t threads[NB_THREADS]; + unsigned long thread_iterations[NB_THREADS]; + for (int i = 0; i < NB_THREADS; ++i) + pthread_create(&threads[i], NULL, worker, NULL); + + // Run for a fixed duration + std::this_thread::sleep_for(std::chrono::seconds(RUN_DURATION_SECONDS)); + atomic_store_explicit(&done, 1, std::memory_order_relaxed); + + for (int i = 0; i < NB_THREADS; ++i) { + void* return_value; + pthread_join(threads[i], &return_value); + thread_iterations[i] = (unsigned long) return_value; + } + + std::cout << "total_iterations=" << iterations_total.load(); + std::cout << ";duration=" << RUN_DURATION_SECONDS; + std::cout << ";nb_threads=" << NB_THREADS; + + // print per thread iterations + for (size_t k = 0u; k < NB_THREADS; ++k) { + std::cout << ";thread_" << k << "=" << thread_iterations[k]; + } + + std::cout << "\n"; + pthread_mutex_destroy(&lock); + + return 0; +} diff --git a/examples/tilt/bench/mutexbench_moderate.cpp b/examples/tilt/bench/mutexbench_moderate.cpp deleted file mode 100644 index a36befce..00000000 --- a/examples/tilt/bench/mutexbench_moderate.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define THREAD_COUNT 8 -#define BENCH_DURATION_SEC 10 - -#define IMPLICIT_INIT - -pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; - -std::mt19937 prng_global{42}; // shared global PRNG -std::atomic done{false}; -std::atomic iterations_total{0}; - -void* worker(void* arg) { - uint64_t iterations_local = 0; - std::mt19937 prng_local{42}; // local PRNG - while (!atomic_load_explicit(&done, std::memory_order_relaxed)) { - pthread_mutex_lock(&lock); - // Critical section - prng_global(); // one step of PRNG in critical section - - pthread_mutex_unlock(&lock); - - // Non-critical section - // (sleep or dummy ops to simulate delay) - ++iterations_local; - } - atomic_fetch_add_explicit(&iterations_total, iterations_local, std::memory_order_relaxed); - return NULL; -} - -int main() { - #if !defined(IMPLICIT_INIT) - if (pthread_mutex_init(&lock, NULL) != 0) { - printf("Mutex initialization failed\n"); - return 1; - } - #endif /* EXPLICIT_INIT */ - - pthread_t threads[THREAD_COUNT]; - for (int i = 0; i < THREAD_COUNT; ++i) - pthread_create(&threads[i], NULL, worker, NULL); - - //sleep(BENCH_DURATION_SEC); - std::this_thread::sleep_for(std::chrono::seconds(BENCH_DURATION_SEC)); - atomic_store_explicit(&done, 1, std::memory_order_relaxed); - - - for (int i = 0; i < THREAD_COUNT; ++i) - pthread_join(threads[i], NULL); - - std::cout << "Total iterations: " << iterations_total.load() << "\n"; - pthread_mutex_destroy(&lock); - - return 0; -} diff --git a/examples/tilt/bench/mutex_test.c b/examples/tilt/bench/test_mutex.c similarity index 100% rename from examples/tilt/bench/mutex_test.c rename to examples/tilt/bench/test_mutex.c diff --git a/examples/tilt/campaign_tilt.py b/examples/tilt/campaign_tilt.py index caf94b1b..97b41053 100755 --- a/examples/tilt/campaign_tilt.py +++ b/examples/tilt/campaign_tilt.py @@ -21,7 +21,7 @@ NB_RUNS = 7 LOCKS = [ - # "clhlock", + # "clhlock", # Does not work "hemlock", "mcslock", "reciplock_impl", @@ -32,68 +32,29 @@ BENCHMARK_DURATION_SECONDS = 10 -# def campaign_test(tilt_lib) -> CampaignCartesianProduct: -# benchmark_name = "mutex_test" -# return CampaignCartesianProduct( -# name=f"tilt_{benchmark_name}", -# benchmark=TestBench( -# src_dir=bench_src_dir, -# shared_libs=[tilt_lib], -# ), -# nb_runs=1, -# variables={ -# "lock": LOCKS + ["taslock", "caslock", "vcaslock-nolse", "vcaslock-lse"], -# }, -# constants={ -# "benchmark_duration_seconds": BENCHMARK_DURATION_SECONDS, -# "benchmark_name": benchmark_name, -# }, -# debug=False, -# gdb=False, -# enable_data_dir=True, -# continuing=False, -# ) - -# def campaign_mutex_bench(tilt_lib) -> CampaignCartesianProduct: -# benchmark_name = "mutex_bench" -# CampaignCartesianProduct( -# name=f"tilt_{benchmark_name}", -# benchmark=MutexBench( -# src_dir=bench_src_dir, -# shared_libs=[tilt_lib], -# ), -# nb_runs=1, -# variables={ -# "lock": LOCKS, -# # "nb_threads": THREADS, -# }, -# constants={ -# "benchmark_name": benchmark_name, -# "benchmark_duration_seconds": BENCHMARK_DURATION_SECONDS, -# }, -# debug=False, -# gdb=False, -# enable_data_dir=True, -# continuing=False, -# benchmark_duration_seconds=BENCHMARK_DURATION_SECONDS, -# pretty={ -# "lock": { -# # "clhlock": "CLH lock", -# "hemlock": "Hemlock", -# "mcslock": "MCS lock", -# "reciplock_impl": "Reciprocating lock", -# "ticketlock": "Ticketlock", -# "twalock": "TWA lock", -# }, -# }, -# ) - -def main() -> None: - tiltlib = TiltLib(tilt_locks_dir=tilt_locks_dir) - tiltlib.build() +def get_campaign_test(tiltlib) -> CampaignCartesianProduct: + benchmark_name = "test_mutex" + return CampaignCartesianProduct( + name="tilt", + benchmark=TestBench( + src_dir=bench_src_dir, + shared_libs=[tiltlib], + ), + nb_runs=1, + variables={ + "lock": LOCKS + ["taslock", "caslock", "vcaslock-nolse", "vcaslock-lse"], + }, + constants={ + "benchmark_name": benchmark_name, + }, + debug=False, + gdb=False, + enable_data_dir=True, + continuing=False, + ) - benchmark_name = "mutex_bench" - campaign_mutex = CampaignCartesianProduct( +def get_campaign_mutex(tiltlib, benchmark_name: str) -> CampaignCartesianProduct: + return CampaignCartesianProduct( name="tilt", benchmark=MutexBench( src_dir=bench_src_dir, @@ -125,31 +86,14 @@ def main() -> None: }, ) - benchmark_name = "mutex_test" - campaign_test = CampaignCartesianProduct( - name="tilt", - benchmark=TestBench( - src_dir=bench_src_dir, - shared_libs=[tiltlib], - ), - nb_runs=1, - variables={ - "lock": LOCKS + ["taslock", "caslock", "vcaslock-nolse", "vcaslock-lse"], - }, - constants={ - "benchmark_name": benchmark_name, - }, - debug=False, - gdb=False, - enable_data_dir=True, - continuing=False, - ) +def main() -> None: + tiltlib = TiltLib(tilt_locks_dir=tilt_locks_dir) + tiltlib.build() campaigns = [ - # campaign_test(tiltlib), - # campaign_mutex_bench(tiltlib), - # campaign_test, - campaign_mutex, + get_campaign_test(tiltlib), + get_campaign_mutex(tiltlib, "bench_mutex"), # High contention + get_campaign_mutex(tiltlib, "bench_mutex_moderate"), # Moderate contention ] suite = CampaignSuite(campaigns=campaigns) diff --git a/examples/tilt/kit/mutex_bench.py b/examples/tilt/kit/mutex_bench.py index 20739107..2418ca68 100644 --- a/examples/tilt/kit/mutex_bench.py +++ b/examples/tilt/kit/mutex_bench.py @@ -19,6 +19,7 @@ from tiltlib import cmake_configure_build class MutexBench(Benchmark): + """Benchmark object for the mutex benchmark as described in the reciprocating locks paper.""" def __init__( self, src_dir: PathType = "", diff --git a/examples/tilt/kit/test_bench.py b/examples/tilt/kit/test_bench.py index 13bafd8a..61d419c2 100644 --- a/examples/tilt/kit/test_bench.py +++ b/examples/tilt/kit/test_bench.py @@ -19,7 +19,7 @@ from tiltlib import cmake_configure_build class SimpleMutexTestBench(Benchmark): - """Benchmark object for testing correctness of our lock implementations for simple mutex operations.""" + """Benchmark object for testing if our lock implementations for simple mutex operations work.""" def __init__( self, src_dir: PathType = "", From f806e17a1fcfb685dcab231e967a75d404fea6f0 Mon Sep 17 00:00:00 2001 From: niels-vdp Date: Mon, 2 Jun 2025 01:56:12 +0100 Subject: [PATCH 3/7] tutorials/leveldb-bench: added lock implementations for reciplock benchmarks --- .../leveldb-bench/campaign_leveldb_locks.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tutorials/leveldb-bench/campaign_leveldb_locks.py b/tutorials/leveldb-bench/campaign_leveldb_locks.py index 0ce97084..484f2eae 100755 --- a/tutorials/leveldb-bench/campaign_leveldb_locks.py +++ b/tutorials/leveldb-bench/campaign_leveldb_locks.py @@ -119,6 +119,40 @@ def get_vcaslock_lse_prefetch_campaign(build_path: Path) -> CampaignCartesianPro shared_libs=[PrecompiledSharedLib(path=vcaslocklib_path, env_vars=None)], ) +def get_hemlock_campaign(build_path: Path) -> CampaignCartesianProduct: + hemlocklib_path = (build_path / "libhemlock.so").resolve() + return get_campaign( + mutex_constant="Hemlock", + shared_libs=[PrecompiledSharedLib(path=hemlocklib_path, env_vars=None)], + ) + +def get_mcslock_campaign(build_path: Path) -> CampaignCartesianProduct: + mcslocklib_path = (build_path / "libmcslock.so").resolve() + return get_campaign( + mutex_constant="MCS lock", + shared_libs=[PrecompiledSharedLib(path=mcslocklib_path, env_vars=None)], + ) + +def get_reciplock_campaign(build_path: Path) -> CampaignCartesianProduct: + reciplocklib_path = (build_path / "libreciplock_impl.so").resolve() + return get_campaign( + mutex_constant="Reciprocating lock", + shared_libs=[PrecompiledSharedLib(path=reciplocklib_path, env_vars=None)], + ) + +def get_ticketlock_campaign(build_path: Path) -> CampaignCartesianProduct: + ticketlocklib_path = (build_path / "libticketlock.so").resolve() + return get_campaign( + mutex_constant="Ticketlock", + shared_libs=[PrecompiledSharedLib(path=ticketlocklib_path, env_vars=None)], + ) + +def get_twalock_campaign(build_path: Path) -> CampaignCartesianProduct: + twalocklib_path = (build_path / "libtwalock.so").resolve() + return get_campaign( + mutex_constant="TWA lock", + shared_libs=[PrecompiledSharedLib(path=twalocklib_path, env_vars=None)], + ) def main() -> None: platform = get_current_platform() @@ -132,6 +166,11 @@ def main() -> None: get_vcaslock_lse_campaign(build_path=build_ok), get_vcaslock_nolse_prefetch_campaign(build_path=build_regression), get_vcaslock_lse_prefetch_campaign(build_path=build_regression), + get_hemlock_campaign(build_path=build_ok), + get_mcslock_campaign(build_path=build_ok), + get_reciplock_campaign(build_path=build_ok), + get_ticketlock_campaign(build_path=build_ok), + get_twalock_campaign(build_path=build_ok), ] suite = CampaignSuite(campaigns=campaigns) suite.print_durations() From 283ce07aeedcad810a1450ad44cd010e57ca1c5e Mon Sep 17 00:00:00 2001 From: niels-vdp Date: Mon, 2 Jun 2025 08:33:01 +0100 Subject: [PATCH 4/7] examples/tilt: licensing --- examples/tilt/bench/bench_mutex.cpp | 6 +++--- examples/tilt/bench/mutex_bench.c | 3 +++ examples/tilt/bench/test_mutex.c | 3 +++ examples/tilt/deps/reciplock/CMakeLists.txt | 3 +++ examples/tilt/deps/reciplock/include/reciplock.h | 5 ++--- examples/tilt/locks/CMakeLists.txt | 3 +++ examples/tilt/locks/clhlock.c | 3 +++ examples/tilt/locks/hemlock.c | 3 +++ examples/tilt/locks/mcslock.c | 3 +++ examples/tilt/locks/reciplock.c | 3 +++ examples/tilt/locks/ticketlock.c | 3 +++ examples/tilt/locks/twalock.c | 3 +++ 12 files changed, 35 insertions(+), 6 deletions(-) diff --git a/examples/tilt/bench/bench_mutex.cpp b/examples/tilt/bench/bench_mutex.cpp index 3b8b637e..4447354e 100644 --- a/examples/tilt/bench/bench_mutex.cpp +++ b/examples/tilt/bench/bench_mutex.cpp @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include #include @@ -10,9 +13,6 @@ #include /* defines NB_THREADS, RUN_DURATION_SECONDS and lock_* operations and types. */ -// #define RUN_DURATION_SECONDS 5 -// #define NB_THREADS 8 - #define IMPLICIT_INIT pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; diff --git a/examples/tilt/bench/mutex_bench.c b/examples/tilt/bench/mutex_bench.c index 1438a8df..ea53a151 100644 --- a/examples/tilt/bench/mutex_bench.c +++ b/examples/tilt/bench/mutex_bench.c @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include #include diff --git a/examples/tilt/bench/test_mutex.c b/examples/tilt/bench/test_mutex.c index 2d37f208..25602b35 100644 --- a/examples/tilt/bench/test_mutex.c +++ b/examples/tilt/bench/test_mutex.c @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include #include diff --git a/examples/tilt/deps/reciplock/CMakeLists.txt b/examples/tilt/deps/reciplock/CMakeLists.txt index 402d6ea6..3e98adfd 100644 --- a/examples/tilt/deps/reciplock/CMakeLists.txt +++ b/examples/tilt/deps/reciplock/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +# SPDX-License-Identifier: MIT + cmake_minimum_required(VERSION 3.10) project( reciplock diff --git a/examples/tilt/deps/reciplock/include/reciplock.h b/examples/tilt/deps/reciplock/include/reciplock.h index f2f684fb..7066ac86 100644 --- a/examples/tilt/deps/reciplock/include/reciplock.h +++ b/examples/tilt/deps/reciplock/include/reciplock.h @@ -1,6 +1,5 @@ -// -// Created by niels on 30/05/2025. -// +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT #ifndef RECIP_RECIPLOCK_H #define RECIP_RECIPLOCK_H diff --git a/examples/tilt/locks/CMakeLists.txt b/examples/tilt/locks/CMakeLists.txt index 0cc2fe8d..0ca6f461 100644 --- a/examples/tilt/locks/CMakeLists.txt +++ b/examples/tilt/locks/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +# SPDX-License-Identifier: MIT + cmake_minimum_required(VERSION 3.10) project(tiltlock C) diff --git a/examples/tilt/locks/clhlock.c b/examples/tilt/locks/clhlock.c index b2c09929..d9b50dfc 100644 --- a/examples/tilt/locks/clhlock.c +++ b/examples/tilt/locks/clhlock.c @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include diff --git a/examples/tilt/locks/hemlock.c b/examples/tilt/locks/hemlock.c index 0ded4ab3..b52440f7 100644 --- a/examples/tilt/locks/hemlock.c +++ b/examples/tilt/locks/hemlock.c @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include diff --git a/examples/tilt/locks/mcslock.c b/examples/tilt/locks/mcslock.c index 37137fe7..1ca29352 100644 --- a/examples/tilt/locks/mcslock.c +++ b/examples/tilt/locks/mcslock.c @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include diff --git a/examples/tilt/locks/reciplock.c b/examples/tilt/locks/reciplock.c index 31cbee80..40c85ab5 100644 --- a/examples/tilt/locks/reciplock.c +++ b/examples/tilt/locks/reciplock.c @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include diff --git a/examples/tilt/locks/ticketlock.c b/examples/tilt/locks/ticketlock.c index 370ce6bf..00b858a1 100644 --- a/examples/tilt/locks/ticketlock.c +++ b/examples/tilt/locks/ticketlock.c @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include diff --git a/examples/tilt/locks/twalock.c b/examples/tilt/locks/twalock.c index 73db8483..06fd2a78 100644 --- a/examples/tilt/locks/twalock.c +++ b/examples/tilt/locks/twalock.c @@ -1,3 +1,6 @@ +// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +// SPDX-License-Identifier: MIT + #include #include From baab1299e730d6d789e76143e3beb6a90f63497a Mon Sep 17 00:00:00 2001 From: niels-vdp Date: Mon, 2 Jun 2025 08:34:30 +0100 Subject: [PATCH 5/7] examples/tilt: renamed reciplock header library --- examples/tilt/campaign_tilt.py | 6 +++--- examples/tilt/deps/reciplock/CMakeLists.txt | 6 +++--- examples/tilt/locks/CMakeLists.txt | 5 +++-- tutorials/leveldb-bench/campaign_leveldb_locks.py | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/tilt/campaign_tilt.py b/examples/tilt/campaign_tilt.py index 97b41053..37835cb4 100755 --- a/examples/tilt/campaign_tilt.py +++ b/examples/tilt/campaign_tilt.py @@ -24,7 +24,7 @@ # "clhlock", # Does not work "hemlock", "mcslock", - "reciplock_impl", + "reciplock", "ticketlock", "twalock", ] @@ -63,7 +63,7 @@ def get_campaign_mutex(tiltlib, benchmark_name: str) -> CampaignCartesianProduct nb_runs=NB_RUNS, variables={ "lock": LOCKS, - "nb_threads": range(1, 5), # 1-4 threads + "nb_threads": range(1, 17), # 1-16 threads }, constants={ "benchmark_name": benchmark_name, @@ -79,7 +79,7 @@ def get_campaign_mutex(tiltlib, benchmark_name: str) -> CampaignCartesianProduct # "clhlock": "CLH lock", "hemlock": "Hemlock", "mcslock": "MCS lock", - "reciplock_impl": "Reciprocating lock", + "reciplock": "Reciprocating lock", "ticketlock": "Ticketlock", "twalock": "TWA lock", }, diff --git a/examples/tilt/deps/reciplock/CMakeLists.txt b/examples/tilt/deps/reciplock/CMakeLists.txt index 3e98adfd..65c9b7ff 100644 --- a/examples/tilt/deps/reciplock/CMakeLists.txt +++ b/examples/tilt/deps/reciplock/CMakeLists.txt @@ -3,11 +3,11 @@ cmake_minimum_required(VERSION 3.10) project( - reciplock + reciplock_headers LANGUAGES C VERSION 2.1.0 ) include_directories(include) -add_library(reciplock INTERFACE) -target_include_directories(reciplock INTERFACE include) +add_library(reciplock_headers INTERFACE) +target_include_directories(reciplock_headers INTERFACE include) diff --git a/examples/tilt/locks/CMakeLists.txt b/examples/tilt/locks/CMakeLists.txt index 0ca6f461..d069155b 100644 --- a/examples/tilt/locks/CMakeLists.txt +++ b/examples/tilt/locks/CMakeLists.txt @@ -6,6 +6,7 @@ cmake_minimum_required(VERSION 3.10) project(tiltlock C) add_subdirectory(${CMAKE_SOURCE_DIR}/../deps/libvsync/ build_libvsync) + add_subdirectory(${CMAKE_SOURCE_DIR}/../deps/reciplock/ build_reciplock) include_directories(include ${CMAKE_SOURCE_DIR}/../deps/tilt/include/) @@ -19,7 +20,7 @@ add_library(hemlock SHARED hemlock.c) add_library(mcslock SHARED mcslock.c) add_library(ticketlock SHARED ticketlock.c) add_library(twalock SHARED twalock.c) -add_library(reciplock_impl SHARED reciplock.c) +add_library(reciplock SHARED reciplock.c) target_link_libraries(vcaslock-lse vsync) target_link_libraries(vcaslock-nolse vsync) @@ -28,7 +29,7 @@ target_link_libraries(hemlock vsync) target_link_libraries(mcslock vsync) target_link_libraries(ticketlock vsync) target_link_libraries(twalock vsync) -target_link_libraries(reciplock_impl reciplock) +target_link_libraries(reciplock reciplock_headers) if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "armv8") target_compile_definitions(vcaslock-nolse PRIVATE VATOMIC_DISABLE_ARM64_LSE) diff --git a/tutorials/leveldb-bench/campaign_leveldb_locks.py b/tutorials/leveldb-bench/campaign_leveldb_locks.py index 484f2eae..449c2dab 100755 --- a/tutorials/leveldb-bench/campaign_leveldb_locks.py +++ b/tutorials/leveldb-bench/campaign_leveldb_locks.py @@ -134,7 +134,7 @@ def get_mcslock_campaign(build_path: Path) -> CampaignCartesianProduct: ) def get_reciplock_campaign(build_path: Path) -> CampaignCartesianProduct: - reciplocklib_path = (build_path / "libreciplock_impl.so").resolve() + reciplocklib_path = (build_path / "libreciplock.so").resolve() return get_campaign( mutex_constant="Reciprocating lock", shared_libs=[PrecompiledSharedLib(path=reciplocklib_path, env_vars=None)], From e19982c8432e46d0712c5fb29f3fb2cb707d5244 Mon Sep 17 00:00:00 2001 From: niels-vdp Date: Mon, 2 Jun 2025 15:37:31 +0200 Subject: [PATCH 6/7] Added TODOs --- examples/tilt/README.md | 8 ++++++-- examples/tilt/bench/bench_atomic.cpp | 4 ++-- examples/tilt/campaign_tilt.py | 2 +- tutorials/leveldb-bench/campaign_leveldb_locks.py | 8 ++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/examples/tilt/README.md b/examples/tilt/README.md index ca1deb68..166f24c5 100644 --- a/examples/tilt/README.md +++ b/examples/tilt/README.md @@ -4,7 +4,6 @@ ```bash cd examples/tilt/ -mkdir deps/ cd deps/ git clone https://github.com/open-s4c/libvsync.git git clone https://github.com/open-s4c/tilt.git @@ -18,8 +17,13 @@ cd ../ . ./venv/bin/activate ``` -## Run simple test campaign +## Run campaign for Reciprocating lock benchmarks ```bash ./campaign_tilt.py ``` + +## Todo + +- [ ] Fix implemenation of CLH lock +- [ ] Fix implementation of the atomic benchmark diff --git a/examples/tilt/bench/bench_atomic.cpp b/examples/tilt/bench/bench_atomic.cpp index c1ed6aff..929cd5f5 100644 --- a/examples/tilt/bench/bench_atomic.cpp +++ b/examples/tilt/bench/bench_atomic.cpp @@ -26,7 +26,7 @@ std::atomic iterations_total{0}; void* worker(void* arg) { S local = {1, 2, 3, 4, 5}; uint64_t iterations_local = 0; - while (!atomic_load_explicit(&done, std::memory_order_relaxed)) { + while (!atomic_load_explicit(&done, std::memory_order_relaxed)) { // TODO: fix pthread_mutex_lock(&lock); // Critical section atomic_exchange_explicit(&shared, local, std::memory_order_seq_cst); // exchange with global @@ -51,7 +51,7 @@ int main() { // Initialize shared atomic with zeros S zeroes = {0, 0, 0, 0, 0}; - shared.store(zeroes, std::memory_order_relaxed); + shared.store(zeroes, std::memory_order_relaxed); // TODO: fix //atomic_store_explicit(&shared, {0, 0, 0, 0, 0}, std::memory_order_relaxed); pthread_t threads[NB_THREADS]; diff --git a/examples/tilt/campaign_tilt.py b/examples/tilt/campaign_tilt.py index 37835cb4..795766c0 100755 --- a/examples/tilt/campaign_tilt.py +++ b/examples/tilt/campaign_tilt.py @@ -21,7 +21,7 @@ NB_RUNS = 7 LOCKS = [ - # "clhlock", # Does not work + # "clhlock", # TODO: fix Tilt implementation "hemlock", "mcslock", "reciplock", diff --git a/tutorials/leveldb-bench/campaign_leveldb_locks.py b/tutorials/leveldb-bench/campaign_leveldb_locks.py index 449c2dab..2439d69c 100755 --- a/tutorials/leveldb-bench/campaign_leveldb_locks.py +++ b/tutorials/leveldb-bench/campaign_leveldb_locks.py @@ -119,6 +119,13 @@ def get_vcaslock_lse_prefetch_campaign(build_path: Path) -> CampaignCartesianPro shared_libs=[PrecompiledSharedLib(path=vcaslocklib_path, env_vars=None)], ) +def get_clhlock_campaign(build_path: Path) -> CampaignCartesianProduct: + clhlocklib_path = (build_path / "libclhlock.so").resolve() + return get_campaign( + mutex_constant="CLH lock", + shared_libs=[PrecompiledSharedLib(path=clhlocklib_path, env_vars=None)], + ) + def get_hemlock_campaign(build_path: Path) -> CampaignCartesianProduct: hemlocklib_path = (build_path / "libhemlock.so").resolve() return get_campaign( @@ -166,6 +173,7 @@ def main() -> None: get_vcaslock_lse_campaign(build_path=build_ok), get_vcaslock_nolse_prefetch_campaign(build_path=build_regression), get_vcaslock_lse_prefetch_campaign(build_path=build_regression), + # get_clhlock_campaign(build_path=build_ok), # TODO: fix Tilt implementation get_hemlock_campaign(build_path=build_ok), get_mcslock_campaign(build_path=build_ok), get_reciplock_campaign(build_path=build_ok), From eb223c2c02fa608462a7d4e5b941259e554c9950 Mon Sep 17 00:00:00 2001 From: niels-vdp Date: Mon, 2 Jun 2025 16:04:48 +0200 Subject: [PATCH 7/7] examples/tilt: split campaign in two, removed obsolete file --- examples/tilt/README.md | 18 ++++- examples/tilt/bench/mutex_bench.c | 59 ----------------- ...ilt.py => campaign_reciprocating_locks.py} | 26 +------- examples/tilt/campaign_test.py | 66 +++++++++++++++++++ 4 files changed, 82 insertions(+), 87 deletions(-) delete mode 100644 examples/tilt/bench/mutex_bench.c rename examples/tilt/{campaign_tilt.py => campaign_reciprocating_locks.py} (75%) create mode 100755 examples/tilt/campaign_test.py diff --git a/examples/tilt/README.md b/examples/tilt/README.md index 166f24c5..6d632b35 100644 --- a/examples/tilt/README.md +++ b/examples/tilt/README.md @@ -1,4 +1,4 @@ -# Tutorial: Tilt +# Tilt ## Clone libvsync and tilt repositories @@ -17,10 +17,22 @@ cd ../ . ./venv/bin/activate ``` -## Run campaign for Reciprocating lock benchmarks +## Campaigns + +### Simple Mutex test + +Runs a simple campaign to run Tilt locks. + +```bash +./campaign_test.py +``` + +### Reciprocating lock benchmarks + +Runs a campaign that runs the benchmarks described in the Reciprocating Locks paper, using the Tilt. ```bash -./campaign_tilt.py +./campaign_reciprocating_locks.py ``` ## Todo diff --git a/examples/tilt/bench/mutex_bench.c b/examples/tilt/bench/mutex_bench.c deleted file mode 100644 index ea53a151..00000000 --- a/examples/tilt/bench/mutex_bench.c +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. -// SPDX-License-Identifier: MIT - -#include -#include -#include - -#define THREAD_COUNT 8 -#define BENCH_DURATION_SEC 10 - -#define IMPLICIT_INIT - -static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; - -static atomic_bool done = false; -static atomic_uint_fast64_t iterations_total = 0; - -void* worker(void* arg) { - uint64_t iterations_local = 0; - while (!atomic_load_explicit(&done, memory_order_relaxed)) { - pthread_mutex_lock(&lock); - // Critical section - // (could do something tiny like a counter or memory op) - pthread_mutex_unlock(&lock); - - // Non-critical section - // (sleep or dummy ops to simulate delay) - ++iterations_local; - } - atomic_fetch_add(&iterations_total, iterations_local); - return NULL; -} - -int main() { - #if !defined(IMPLICIT_INIT) - if (pthread_mutex_init(&lock, NULL) != 0) { - printf("Mutex initialization failed\n"); - return 1; - } - #endif /* EXPLICIT_INIT */ - - pthread_t threads[THREAD_COUNT]; - - for (int i = 0; i < THREAD_COUNT; i++) { - pthread_create(&threads[i], NULL, worker, NULL); - } - - sleep(BENCH_DURATION_SEC); - atomic_store(&done, true); - - for (int i = 0; i < THREAD_COUNT; i++) { - pthread_join(threads[i], NULL); - } - - printf("Total iterations: %lu\n", atomic_load(&iterations_total)); - pthread_mutex_destroy(&lock); - - return 0; -} diff --git a/examples/tilt/campaign_tilt.py b/examples/tilt/campaign_reciprocating_locks.py similarity index 75% rename from examples/tilt/campaign_tilt.py rename to examples/tilt/campaign_reciprocating_locks.py index 795766c0..4d3e9aff 100755 --- a/examples/tilt/campaign_tilt.py +++ b/examples/tilt/campaign_reciprocating_locks.py @@ -2,13 +2,11 @@ # Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. # SPDX-License-Identifier: MIT """ -Minimal example to run tilt locks. +Campaign to run the Reciprocating locks benchmarks using Tilt. """ -# from tiltlib import SimpleMutexTestBench as Bench from tiltlib import TiltLib -from test_bench import SimpleMutexTestBench as TestBench from mutex_bench import MutexBench from benchkit.campaign import CampaignCartesianProduct, CampaignSuite @@ -32,27 +30,6 @@ BENCHMARK_DURATION_SECONDS = 10 -def get_campaign_test(tiltlib) -> CampaignCartesianProduct: - benchmark_name = "test_mutex" - return CampaignCartesianProduct( - name="tilt", - benchmark=TestBench( - src_dir=bench_src_dir, - shared_libs=[tiltlib], - ), - nb_runs=1, - variables={ - "lock": LOCKS + ["taslock", "caslock", "vcaslock-nolse", "vcaslock-lse"], - }, - constants={ - "benchmark_name": benchmark_name, - }, - debug=False, - gdb=False, - enable_data_dir=True, - continuing=False, - ) - def get_campaign_mutex(tiltlib, benchmark_name: str) -> CampaignCartesianProduct: return CampaignCartesianProduct( name="tilt", @@ -91,7 +68,6 @@ def main() -> None: tiltlib.build() campaigns = [ - get_campaign_test(tiltlib), get_campaign_mutex(tiltlib, "bench_mutex"), # High contention get_campaign_mutex(tiltlib, "bench_mutex_moderate"), # Moderate contention ] diff --git a/examples/tilt/campaign_test.py b/examples/tilt/campaign_test.py new file mode 100755 index 00000000..25ce33a0 --- /dev/null +++ b/examples/tilt/campaign_test.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# Copyright (C) 2025 Vrije Universiteit Brussel. All rights reserved. +# SPDX-License-Identifier: MIT +""" +Minimal example to run tilt locks. +""" + + +from tiltlib import TiltLib +from test_bench import SimpleMutexTestBench as TestBench + +from benchkit.campaign import CampaignCartesianProduct, CampaignSuite +from benchkit.utils.dir import caller_dir + +campaign_dir = caller_dir() +tilt_locks_dir = campaign_dir / "locks" +bench_src_dir = campaign_dir / "bench" +vsync_dir = (tilt_locks_dir / "../deps/libvsync/").resolve() + +NB_RUNS = 1 +LOCKS = [ + # "clhlock", # TODO: fix Tilt implementation + "hemlock", + "mcslock", + "reciplock", + "ticketlock", + "twalock", +] + + +def get_campaign_test(tiltlib) -> CampaignCartesianProduct: + benchmark_name = "test_mutex" + return CampaignCartesianProduct( + name="tilt", + benchmark=TestBench( + src_dir=bench_src_dir, + shared_libs=[tiltlib], + ), + nb_runs=NB_RUNS, + variables={ + "lock": LOCKS + ["taslock", "caslock", "vcaslock-nolse", "vcaslock-lse"], + }, + constants={ + "benchmark_name": benchmark_name, + }, + debug=False, + gdb=False, + enable_data_dir=True, + continuing=False, + ) + +def main() -> None: + tiltlib = TiltLib(tilt_locks_dir=tilt_locks_dir) + tiltlib.build() + + campaigns = [ + get_campaign_test(tiltlib), + ] + + suite = CampaignSuite(campaigns=campaigns) + suite.print_durations() + suite.run_suite() + + +if __name__ == "__main__": + main()