178b4b5ccSLouis Dionne //===----------------------------------------------------------------------===// 26a54dfbfSLouis Dionne // 378b4b5ccSLouis Dionne // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 478b4b5ccSLouis Dionne // See https://llvm.org/LICENSE.txt for license information. 578b4b5ccSLouis Dionne // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 678b4b5ccSLouis Dionne // 778b4b5ccSLouis Dionne //===----------------------------------------------------------------------===// 878b4b5ccSLouis Dionne 9b2d24947SLouis Dionne // UNSUPPORTED: c++03, c++11, c++14, c++17 10b2d24947SLouis Dionne 1178b4b5ccSLouis Dionne #include <atomic> 12*9ebc6f5dSNikolas Klauser #include <cstdint> 1378b4b5ccSLouis Dionne #include <mutex> 14b2d24947SLouis Dionne #include <stop_token> 1578b4b5ccSLouis Dionne #include <thread> 1678b4b5ccSLouis Dionne 1778b4b5ccSLouis Dionne #include "benchmark/benchmark.h" 1878b4b5ccSLouis Dionne #include "make_test_thread.h" 1978b4b5ccSLouis Dionne 2078b4b5ccSLouis Dionne using namespace std::chrono_literals; 2178b4b5ccSLouis Dionne 2278b4b5ccSLouis Dionne struct AtomicLock { 2378b4b5ccSLouis Dionne std::atomic<bool>& locked_; 2478b4b5ccSLouis Dionne 2578b4b5ccSLouis Dionne AtomicLock(const AtomicLock&) = delete; 2678b4b5ccSLouis Dionne AtomicLock& operator=(const AtomicLock&) = delete; 2778b4b5ccSLouis Dionne 2878b4b5ccSLouis Dionne AtomicLock(std::atomic<bool>& l) : locked_(l) { lock(); } 2978b4b5ccSLouis Dionne ~AtomicLock() { unlock(); } 3078b4b5ccSLouis Dionne 3178b4b5ccSLouis Dionne void lock() { 3278b4b5ccSLouis Dionne while (true) { 3378b4b5ccSLouis Dionne locked_.wait(true, std::memory_order_relaxed); 3478b4b5ccSLouis Dionne bool expected = false; 3578b4b5ccSLouis Dionne if (locked_.compare_exchange_weak(expected, true, std::memory_order_acquire, std::memory_order_relaxed)) 3678b4b5ccSLouis Dionne break; 3778b4b5ccSLouis Dionne } 3878b4b5ccSLouis Dionne } 3978b4b5ccSLouis Dionne 4078b4b5ccSLouis Dionne void unlock() { 4178b4b5ccSLouis Dionne locked_.store(false, std::memory_order_release); 4278b4b5ccSLouis Dionne locked_.notify_all(); 4378b4b5ccSLouis Dionne } 4478b4b5ccSLouis Dionne }; 4578b4b5ccSLouis Dionne 4678b4b5ccSLouis Dionne // using LockState = std::atomic<bool>; 4778b4b5ccSLouis Dionne // using Lock = AtomicLock; 4878b4b5ccSLouis Dionne 4978b4b5ccSLouis Dionne // using LockState = std::mutex; 5078b4b5ccSLouis Dionne // using Lock = std::unique_lock<std::mutex>; 5178b4b5ccSLouis Dionne 5278b4b5ccSLouis Dionne template <class LockState, class Lock> 5378b4b5ccSLouis Dionne void test_multi_thread_lock_unlock(benchmark::State& state) { 5478b4b5ccSLouis Dionne std::uint64_t total_loop_test_param = state.range(0); 5578b4b5ccSLouis Dionne constexpr auto num_threads = 15; 5678b4b5ccSLouis Dionne std::vector<std::jthread> threads; 5778b4b5ccSLouis Dionne threads.reserve(num_threads); 5878b4b5ccSLouis Dionne 5978b4b5ccSLouis Dionne std::atomic<std::uint64_t> start_flag = 0; 6078b4b5ccSLouis Dionne std::atomic<std::uint64_t> done_count = 0; 6178b4b5ccSLouis Dionne 6278b4b5ccSLouis Dionne LockState lock_state{}; 6378b4b5ccSLouis Dionne 6478b4b5ccSLouis Dionne auto func = [&start_flag, &done_count, &lock_state, total_loop_test_param](std::stop_token st) { 6578b4b5ccSLouis Dionne auto old_start = 0; 6678b4b5ccSLouis Dionne while (!st.stop_requested()) { 6778b4b5ccSLouis Dionne start_flag.wait(old_start); 6878b4b5ccSLouis Dionne old_start = start_flag.load(); 6978b4b5ccSLouis Dionne 7078b4b5ccSLouis Dionne // main things under test: locking and unlocking in the loop 7178b4b5ccSLouis Dionne for (std::uint64_t i = 0; i < total_loop_test_param; ++i) { 7278b4b5ccSLouis Dionne Lock l{lock_state}; 7378b4b5ccSLouis Dionne } 7478b4b5ccSLouis Dionne 7578b4b5ccSLouis Dionne done_count.fetch_add(1); 7678b4b5ccSLouis Dionne } 7778b4b5ccSLouis Dionne }; 7878b4b5ccSLouis Dionne 7978b4b5ccSLouis Dionne for (size_t i = 0; i < num_threads; ++i) { 8078b4b5ccSLouis Dionne threads.emplace_back(support::make_test_jthread(func)); 8178b4b5ccSLouis Dionne } 8278b4b5ccSLouis Dionne 8378b4b5ccSLouis Dionne for (auto _ : state) { 8478b4b5ccSLouis Dionne done_count = 0; 8578b4b5ccSLouis Dionne start_flag.fetch_add(1); 8678b4b5ccSLouis Dionne start_flag.notify_all(); 8778b4b5ccSLouis Dionne while (done_count < num_threads) { 8878b4b5ccSLouis Dionne std::this_thread::yield(); 8978b4b5ccSLouis Dionne } 9078b4b5ccSLouis Dionne } 9178b4b5ccSLouis Dionne for (auto& t : threads) { 9278b4b5ccSLouis Dionne t.request_stop(); 9378b4b5ccSLouis Dionne } 9478b4b5ccSLouis Dionne start_flag.fetch_add(1); 9578b4b5ccSLouis Dionne start_flag.notify_all(); 9678b4b5ccSLouis Dionne for (auto& t : threads) { 9778b4b5ccSLouis Dionne t.join(); 9878b4b5ccSLouis Dionne } 9978b4b5ccSLouis Dionne } 10078b4b5ccSLouis Dionne 10178b4b5ccSLouis Dionne void BM_atomic_wait(benchmark::State& state) { test_multi_thread_lock_unlock<std::atomic<bool>, AtomicLock>(state); } 10278b4b5ccSLouis Dionne BENCHMARK(BM_atomic_wait)->RangeMultiplier(2)->Range(1 << 10, 1 << 20); 10378b4b5ccSLouis Dionne 10478b4b5ccSLouis Dionne void BM_mutex(benchmark::State& state) { 10578b4b5ccSLouis Dionne test_multi_thread_lock_unlock<std::mutex, std::unique_lock<std::mutex>>(state); 10678b4b5ccSLouis Dionne } 10778b4b5ccSLouis Dionne BENCHMARK(BM_mutex)->RangeMultiplier(2)->Range(1 << 10, 1 << 20); 10878b4b5ccSLouis Dionne 10978b4b5ccSLouis Dionne BENCHMARK_MAIN(); 110