Skip to content

Commit 3fb08cb

Browse files
Adding queue benchmark
1 parent 1483499 commit 3fb08cb

5 files changed

Lines changed: 139 additions & 18 deletions

File tree

.bazelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ build:ferrocene-coverage --remote_download_all
9090
# to be removed
9191
build:build_qnx8 --config=arm64-qnx
9292

93+
# Benchmark configuration — optimized build with no debug symbols
94+
build:bench -c opt
95+
9396
## default is a stdout logger which looks like dlt logs
9497
## uncomment below to use score::mw::log instead of the stdout logger
9598
# build --cxxopt=-DLC_LOG_SCORE_MW_LOG

MODULE.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ bazel_dep(name = "flatbuffers", version = "25.12.19")
2929
bazel_dep(name = "download_utils", version = "1.2.2")
3030
bazel_dep(name = "googletest", version = "1.17.0.bcr.2")
3131

32+
bazel_dep(name = "google_benchmark", version = "1.9.5", dev_dependency = True)
33+
3234
# S-CORE process rules
3335

3436
bazel_dep(name = "score_bazel_platforms", version = "0.1.2", dev_dependency = True)

score/launch_manager/daemon/src/common/concurrency/BUILD

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# SPDX-License-Identifier: Apache-2.0
1212
# *******************************************************************************
13-
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
13+
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
1414

1515
cc_library(
1616
name = "workerthread",
@@ -46,6 +46,15 @@ cc_library(
4646
],
4747
)
4848

49+
cc_binary(
50+
name = "mpmc_concurrent_queue_bench",
51+
srcs = ["mpmc_concurrent_queue_bench.cpp"],
52+
deps = [
53+
":mpmc_concurrent_queue",
54+
"@google_benchmark//:benchmark_main",
55+
],
56+
)
57+
4958
cc_test(
5059
name = "mpmc_concurrent_queue_test",
5160
srcs = ["mpmc_concurrent_queue_test.cpp"],

score/launch_manager/daemon/src/common/concurrency/mpmc_concurrent_queue.hpp

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -273,36 +273,22 @@ class MPMCConcurrentQueue
273273
return score::cpp::blank{};
274274
}
275275

276-
/// @brief Helper type to align members to the cache lines.
277-
/// @details Using padding rather than alignas(CacheLineSize) so that
278-
/// ASan's fake stack doesn't cause UBSan misalignment.
279-
template <class Atomic>
280-
struct CacheLinePaddedAtomic : public std::atomic<Atomic>
281-
{
282-
static_assert(sizeof(std::atomic<Atomic>) < CacheLineSize,
283-
"atomic<Atomic> is too large to pad to one cache line");
284-
using std::atomic<Atomic>::atomic;
285-
286-
private:
287-
char _pad[CacheLineSize - sizeof(std::atomic<Atomic>)]{};
288-
};
289-
290276
/// @brief Underlying storage.
291277
std::array<Slot, Capacity> m_slots;
292278

293279
/// @brief The front of the queue; claimed by consumers via fetch_add in pop.
294280
/// @details Aligned so that m_head and m_tail do not share a cache line.
295-
CacheLinePaddedAtomic<std::size_t> m_head{0};
281+
std::atomic<std::size_t> m_head{0};
296282

297283
/// @brief The back of the queue; claimed by producers via fetch_add in push_impl.
298284
/// @details Aligned so that m_head and m_tail do not share a cache line.
299-
CacheLinePaddedAtomic<std::size_t> m_tail{0};
285+
std::atomic<std::size_t> m_tail{0};
300286

301287
/// @brief Set to true by stop(); causes push() to return false and pop() to
302288
/// return std::nullopt instead of blocking.
303289
/// @details Aligned on its own cache line so that the single stop() write
304290
/// does not cause false sharing with m_tail updates in push_impl().
305-
CacheLinePaddedAtomic<bool> m_stopped{false};
291+
std::atomic<bool> m_stopped{false};
306292

307293
/// @brief Counts items currently in the queue; consumers block on this when
308294
/// the queue is empty.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/********************************************************************************
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* SPDX-License-Identifier: Apache-2.0
12+
********************************************************************************/
13+
14+
#include "score/mw/launch_manager/common/concurrency/mpmc_concurrent_queue.hpp"
15+
16+
#include <benchmark/benchmark.h>
17+
18+
#include <atomic>
19+
#include <cstdint>
20+
#include <thread>
21+
#include <vector>
22+
23+
using namespace score::lcm::internal;
24+
25+
constexpr std::uint64_t g_items_per_prod = 10'000;
26+
27+
template <std::size_t Capacity>
28+
static void BM_MPMC(benchmark::State& state)
29+
{
30+
const std::int64_t n_producers = state.range(0);
31+
const std::int64_t n_consumers = state.range(1);
32+
33+
for (auto _ : state)
34+
{
35+
// pause for init so allocs don't affect the results.
36+
state.PauseTiming();
37+
38+
MPMCConcurrentQueue<std::uint64_t, Capacity> queue;
39+
const std::uint64_t total = static_cast<std::uint64_t>(n_producers) * g_items_per_prod;
40+
41+
std::atomic<std::uint64_t> consumed{0};
42+
std::atomic<bool> run{false};
43+
44+
std::vector<std::thread> producers;
45+
producers.reserve(static_cast<std::size_t>(n_producers));
46+
47+
for (int prod_index = 0; prod_index < n_producers; ++prod_index)
48+
{
49+
producers.emplace_back([&]() {
50+
while (!run.load(std::memory_order_acquire))
51+
{
52+
std::this_thread::yield();
53+
}
54+
for (std::uint64_t i = 0; i < g_items_per_prod; ++i)
55+
{
56+
while (!queue.push(i))
57+
{
58+
}
59+
}
60+
});
61+
}
62+
63+
std::vector<std::thread> consumers;
64+
consumers.reserve(static_cast<std::size_t>(n_consumers));
65+
66+
for (int cons_index = 0; cons_index < n_consumers; ++cons_index)
67+
{
68+
consumers.emplace_back([&]() {
69+
while (!run.load(std::memory_order_acquire))
70+
{
71+
std::this_thread::yield();
72+
}
73+
while (consumed.load(std::memory_order_relaxed) < total)
74+
{
75+
if (queue.pop().has_value())
76+
{
77+
consumed.fetch_add(1, std::memory_order_relaxed);
78+
}
79+
}
80+
});
81+
}
82+
83+
state.ResumeTiming();
84+
run.store(true, std::memory_order_release);
85+
86+
for (auto& thread : producers)
87+
{
88+
thread.join();
89+
}
90+
while (consumed.load(std::memory_order_relaxed) < total)
91+
{
92+
std::this_thread::yield();
93+
}
94+
static_cast<void>(queue.stop());
95+
for (auto& thread : consumers)
96+
{
97+
thread.join();
98+
}
99+
100+
state.SetItemsProcessed(static_cast<int64_t>(total));
101+
}
102+
}
103+
104+
// use a variary of consumers and producers
105+
#define REGISTER_MPMC_BENCH(CAP) \
106+
BENCHMARK_TEMPLATE(BM_MPMC, CAP) \
107+
->ArgNames({"producers", "consumers"}) \
108+
->Args({1, 1}) \
109+
->Args({2, 2}) \
110+
->Args({4, 4}) \
111+
->Args({4, 1}) \
112+
->Args({1, 4}) \
113+
->UseRealTime()
114+
115+
REGISTER_MPMC_BENCH(16);
116+
REGISTER_MPMC_BENCH(64);
117+
REGISTER_MPMC_BENCH(256);
118+
REGISTER_MPMC_BENCH(1024);
119+
REGISTER_MPMC_BENCH(4096);
120+
121+
BENCHMARK_MAIN();

0 commit comments

Comments
 (0)