|
| 1 | +#include <atomic> |
| 2 | +#include <chrono> |
| 3 | +#include <cstdint> |
| 4 | +#include <iostream> |
| 5 | +#include <mutex> |
| 6 | +#include <thread> |
| 7 | +#include <utility> |
| 8 | +#include <vector> |
| 9 | +#include <shared_mutex> |
| 10 | + |
| 11 | +#include <folly/Benchmark.h> |
| 12 | +#include <folly/init/Init.h> |
| 13 | +#include <folly/synchronization/Rcu.h> |
| 14 | +#include <gflags/gflags.h> |
| 15 | + |
| 16 | +std::atomic<int> current_value{1}; |
| 17 | + |
| 18 | +DEFINE_bool(verify, false, "Enable correctness checks"); |
| 19 | + |
| 20 | +DEFINE_int32(readers, |
| 21 | + std::thread::hardware_concurrency() > 1 |
| 22 | + ? int(std::thread::hardware_concurrency() - 1) |
| 23 | + : 1, |
| 24 | + "Number of reader threads"); |
| 25 | + |
| 26 | +DEFINE_int32(writers, 1, "Number of writer threads"); |
| 27 | + |
| 28 | +DEFINE_int32(writer_duration_ms, 2000, |
| 29 | + "Scenario duration (ms) for readers/writers"); |
| 30 | + |
| 31 | +DEFINE_int32(writer_pause_ns, 1, |
| 32 | + "Pause between writer updates (ns) outside critical section"); |
| 33 | + |
| 34 | +// ---------- API |
| 35 | + |
| 36 | +struct alignas(std::hardware_destructive_interference_size) T : folly::hazptr_obj_base<T> { |
| 37 | + int x; |
| 38 | + T(int x): x(x) {} |
| 39 | +}; |
| 40 | + |
| 41 | +std::atomic<T*> src = nullptr; |
| 42 | + |
| 43 | +template <typename Fn> requires std::invocable<Fn&, const T*> |
| 44 | +auto readAndAccess(Fn&& fn) { |
| 45 | + thread_local auto h = folly::make_hazard_pointer<>(); |
| 46 | + T *srcptr = h.protect(src); |
| 47 | + return std::invoke(std::forward<Fn>(fn), srcptr); |
| 48 | +} |
| 49 | + |
| 50 | +void update(std::unique_ptr<T> newptr) { |
| 51 | + T *oldptr = src.exchange(newptr.release(), std::memory_order_relaxed); |
| 52 | + if (oldptr) |
| 53 | + oldptr->retire(); |
| 54 | +} |
| 55 | + |
| 56 | +// ---------- |
| 57 | + |
| 58 | +struct alignas(std::hardware_destructive_interference_size) ScenarioStats { |
| 59 | + std::uint64_t reads = 0; |
| 60 | + std::uint64_t failedChecks = 0; |
| 61 | +}; |
| 62 | + |
| 63 | +void reset_state() { |
| 64 | + src.store(nullptr); |
| 65 | + current_value.store(1, std::memory_order_relaxed); |
| 66 | +} |
| 67 | + |
| 68 | +ScenarioStats run_mutex_scenario(std::size_t numReaders, |
| 69 | + std::size_t numWriters) { |
| 70 | + reset_state(); |
| 71 | + |
| 72 | + std::atomic<bool> stop{false}; |
| 73 | + std::vector<ScenarioStats> perReader(numReaders); |
| 74 | + |
| 75 | + std::vector<std::thread> readerThreads; |
| 76 | + readerThreads.reserve(numReaders); |
| 77 | + |
| 78 | + for (std::size_t i = 0; i < numReaders; ++i) { |
| 79 | + readerThreads.emplace_back([&, idx = i] { |
| 80 | + auto& local = perReader[idx]; |
| 81 | + |
| 82 | + while (!stop.load(std::memory_order_relaxed)) { |
| 83 | + int v = readAndAccess([&](const T* ptr) { |
| 84 | + int local_v = 0; |
| 85 | + |
| 86 | + if (ptr) { |
| 87 | + local_v = ptr->x; |
| 88 | + |
| 89 | + if (FLAGS_verify) { |
| 90 | + int cur = current_value.load(std::memory_order_acquire); |
| 91 | + if (local_v < 1 || local_v > cur) { |
| 92 | + ++local.failedChecks; |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + ++local.reads; |
| 98 | + return local_v; |
| 99 | + }); |
| 100 | + |
| 101 | + folly::doNotOptimizeAway(v); |
| 102 | + } |
| 103 | + }); |
| 104 | + } |
| 105 | + |
| 106 | + std::vector<std::thread> writerThreads; |
| 107 | + writerThreads.reserve(numWriters); |
| 108 | + |
| 109 | + auto pause = std::chrono::nanoseconds(FLAGS_writer_pause_ns); |
| 110 | + |
| 111 | + for (std::size_t w = 0; w < numWriters; ++w) { |
| 112 | + writerThreads.emplace_back([&] { |
| 113 | + while (!stop.load(std::memory_order_relaxed)) { |
| 114 | + int next = |
| 115 | + current_value.fetch_add(1, std::memory_order_acq_rel); |
| 116 | + auto t = std::make_unique<T>(next); |
| 117 | + update(std::move(t)); |
| 118 | + |
| 119 | + if (pause.count() > 0) { |
| 120 | + std::this_thread::sleep_for(pause); |
| 121 | + } |
| 122 | + } |
| 123 | + }); |
| 124 | + } |
| 125 | + |
| 126 | + auto duration = std::chrono::milliseconds(FLAGS_writer_duration_ms); |
| 127 | + std::this_thread::sleep_for(duration); |
| 128 | + |
| 129 | + stop.store(true, std::memory_order_relaxed); |
| 130 | + |
| 131 | + for (auto& th : writerThreads) |
| 132 | + th.join(); |
| 133 | + for (auto& th : readerThreads) |
| 134 | + th.join(); |
| 135 | + |
| 136 | + ScenarioStats total{}; |
| 137 | + for (auto& r : perReader) { |
| 138 | + total.reads += r.reads; |
| 139 | + total.failedChecks += r.failedChecks; |
| 140 | + } |
| 141 | + |
| 142 | + return total; |
| 143 | +} |
| 144 | + |
| 145 | +BENCHMARK(MutexRW, iters) { |
| 146 | + ScenarioStats agg{}; |
| 147 | + |
| 148 | + const auto runs = iters; |
| 149 | + const std::size_t numReaders = static_cast<std::size_t>(FLAGS_readers); |
| 150 | + const std::size_t numWriters = static_cast<std::size_t>(FLAGS_writers); |
| 151 | + |
| 152 | + for (std::uint32_t i = 0; i < runs; ++i) { |
| 153 | + auto stats = run_mutex_scenario(numReaders, numWriters); |
| 154 | + agg.reads += stats.reads; |
| 155 | + agg.failedChecks += stats.failedChecks; |
| 156 | + } |
| 157 | + |
| 158 | + if (FLAGS_verify && agg.failedChecks != 0) { |
| 159 | + std::cerr << "[sptr] failed checks: " << agg.failedChecks << "\n"; |
| 160 | + std::abort(); |
| 161 | + } |
| 162 | + |
| 163 | + const double total_ms = |
| 164 | + static_cast<double>(runs) * static_cast<double>(FLAGS_writer_duration_ms); |
| 165 | + const double total_sec = total_ms / 1000.0; |
| 166 | + |
| 167 | + double rps_total = 0.0; |
| 168 | + double rps_per_reader = 0.0; |
| 169 | + |
| 170 | + if (total_sec > 0.0) { |
| 171 | + rps_total = static_cast<double>(agg.reads) / total_sec; |
| 172 | + if (numReaders > 0) { |
| 173 | + rps_per_reader = |
| 174 | + static_cast<double>(agg.reads) / (numReaders * total_sec); |
| 175 | + } |
| 176 | + } |
| 177 | + |
| 178 | + std::cout << "[sptr] bmk results\n"; |
| 179 | + std::cout << "readers=" << numReaders << "\n"; |
| 180 | + std::cout << "rps_per_reader=" << rps_per_reader << "\n"; |
| 181 | +} |
| 182 | + |
| 183 | +int main(int argc, char** argv) { |
| 184 | + folly::Init init(&argc, &argv); |
| 185 | + folly::runBenchmarks(); |
| 186 | +} |
0 commit comments