Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
thread.test.cpp
Go to the documentation of this file.
1#include "thread.hpp"
3#include <atomic>
4#include <gtest/gtest.h>
5#include <set>
6#include <thread>
7
8namespace bb {
9
10class ThreadTest : public ::testing::Test {
11 protected:
12 void SetUp() override
13 {
14 // Store original concurrency for restoration
16 }
17
18 void TearDown() override
19 {
20 // Restore original concurrency
22 }
23
25};
26
27// Test basic parallel_for functionality
28TEST_F(ThreadTest, BasicParallelFor)
29{
30 constexpr size_t num_iterations = 100;
31 std::vector<char> flags(num_iterations, 0);
32
33 parallel_for(num_iterations, [&](size_t i) { flags[i] = 1; });
34
35 // All iterations should have been executed
36 for (size_t i = 0; i < num_iterations; ++i) {
37 EXPECT_TRUE(flags[i]);
38 }
39}
40
41// Test nested parallel_for
42TEST_F(ThreadTest, NestedParallelFor)
43{
44 constexpr size_t outer_iterations = 4;
45 constexpr size_t inner_iterations = 10;
46
47 std::vector<std::vector<char>> flags(outer_iterations, std::vector<char>(inner_iterations, 0));
48
49 parallel_for(outer_iterations,
50 [&](size_t i) { parallel_for(inner_iterations, [&](size_t j) { flags[i][j] = 1; }); });
51
52 // All iterations should have been executed
53 for (size_t i = 0; i < outer_iterations; ++i) {
54 for (size_t j = 0; j < inner_iterations; ++j) {
55 EXPECT_TRUE(flags[i][j]);
56 }
57 }
58}
59
60// Test thread count calculation
61TEST_F(ThreadTest, CalculateNumThreads)
62{
64
65 // With default min iterations per thread (16)
66 // 160 iterations / 16 = 10 desired threads, min(10, 8) = 8
67 EXPECT_EQ(calculate_num_threads(160), 8);
68
69 // 64 iterations / 16 = 4 desired threads, min(4, 8) = 4
70 EXPECT_EQ(calculate_num_threads(64), 4);
71
72 // 8 iterations / 16 = 0 desired threads, but should be at least 1
73 EXPECT_EQ(calculate_num_threads(8), 1);
74
75 // Custom min iterations per thread
76 // 100 iterations / 10 = 10 desired threads, min(10, 8) = 8
77 EXPECT_EQ(calculate_num_threads(100, 10), 8);
78
79 // 30 iterations / 10 = 3 desired threads, min(3, 8) = 3
80 EXPECT_EQ(calculate_num_threads(30, 10), 3);
81}
82
83// Test thread count calculation with power of 2
84TEST_F(ThreadTest, CalculateNumThreadsPow2)
85{
87
88 // With default min iterations per thread (16)
89 // 160 iterations / 16 = 10 desired, nearest power of 2 is 8, min(8, 8) = 8
90 EXPECT_EQ(calculate_num_threads_pow2(160), 8);
91
92 // 64 iterations / 16 = 4 desired, power of 2 is 4, min(4, 8) = 4
93 EXPECT_EQ(calculate_num_threads_pow2(64), 4);
94
95 // 96 iterations / 16 = 6 desired, nearest power of 2 is 4, min(4, 8) = 4
96 EXPECT_EQ(calculate_num_threads_pow2(96), 4);
97
98 // 8 iterations / 16 = 0 desired, should be at least 1
99 EXPECT_EQ(calculate_num_threads_pow2(8), 1);
100}
101
102// Test parallel_for with zero iterations
103TEST_F(ThreadTest, ZeroIterations)
104{
105 size_t counter = 0;
106
107 parallel_for(0, [&](size_t) { counter++; });
108
109 EXPECT_EQ(counter, 0);
110}
111
112// Test parallel_for with one iteration
113TEST_F(ThreadTest, OneIteration)
114{
115 size_t counter = 0;
116
117 parallel_for(1, [&](size_t i) {
118 counter++;
119 EXPECT_EQ(i, 0);
120 });
121
122 EXPECT_EQ(counter, 1);
123}
124
125// Test parallel_for_range
126TEST_F(ThreadTest, ParallelForRange)
127{
128 constexpr size_t num_points = 100;
129 std::vector<char> flags(num_points, 0);
130
131 parallel_for_range(num_points, [&](size_t start, size_t end) {
132 for (size_t i = start; i < end; ++i) {
133 flags[i] = 1;
134 }
135 });
136
137 // All iterations should have been executed
138 for (size_t i = 0; i < num_points; ++i) {
139 EXPECT_TRUE(flags[i]);
140 }
141}
142
143// Test parallel_for_range with threshold
144TEST_F(ThreadTest, ParallelForRangeThreshold)
145{
146 constexpr size_t num_points = 10;
147 std::vector<char> flags(num_points, 0);
148
149 std::atomic<size_t> call_count{ 0 };
150
151 // Set threshold to 10, so with exactly 10 points it should run sequentially (1 call)
153 num_points,
154 [&](size_t start, size_t end) {
155 call_count++;
156 for (size_t i = start; i < end; ++i) {
157 flags[i] = 1;
158 }
159 },
160 10);
161
162 // All iterations should have been executed
163 for (size_t i = 0; i < num_points; ++i) {
164 EXPECT_TRUE(flags[i]);
165 }
166
167 // Should have been called exactly once (sequential)
168 EXPECT_EQ(call_count, 1);
169}
170
171// Test get_num_cpus with different hardware concurrency values
172TEST_F(ThreadTest, HardwareConcurrency)
173{
175 EXPECT_EQ(get_num_cpus(), 1);
176
178 EXPECT_EQ(get_num_cpus(), 4);
179
181 EXPECT_EQ(get_num_cpus(), 16);
182
184 EXPECT_EQ(get_num_cpus(), 128);
185}
186
187// Test get_num_cpus_pow2
188TEST_F(ThreadTest, HardwareConcurrencyPow2)
189{
191 EXPECT_EQ(get_num_cpus_pow2(), 1);
192
194 EXPECT_EQ(get_num_cpus_pow2(), 4);
195
197 EXPECT_EQ(get_num_cpus_pow2(), 4); // Round down to power of 2
198
200 EXPECT_EQ(get_num_cpus_pow2(), 4); // Round down to power of 2
201
203 EXPECT_EQ(get_num_cpus_pow2(), 8);
204
206 EXPECT_EQ(get_num_cpus_pow2(), 8); // Round down to power of 2
207
209 EXPECT_EQ(get_num_cpus_pow2(), 16);
210}
211
212// Test main thread concurrency isolation and nested concurrency
213TEST_F(ThreadTest, ConcurrencyIsolation)
214{
216
217 // Main thread concurrency should be preserved before/after parallel_for
218 size_t cpus_before = get_num_cpus();
219 EXPECT_EQ(cpus_before, 8);
220
221 std::vector<std::atomic<size_t>> observed_inner_cpus(4);
222
223 parallel_for(4, [&](size_t outer_idx) {
224 // Worker threads get their own thread_local concurrency set by the pool
225 // With 8 CPUs and 4 outer tasks, each gets at least 2 CPUs for inner work
226 size_t inner_cpus = get_num_cpus();
227 observed_inner_cpus[outer_idx].store(inner_cpus);
228
229 // Run a nested parallel_for to verify inner concurrency works
230 parallel_for(10, [](size_t) {});
231 });
232
233 // All inner parallel_for calls should see at least 2 CPUs
234 for (size_t i = 0; i < 4; ++i) {
235 EXPECT_GE(observed_inner_cpus[i].load(), 2);
236 }
237
238 // Main thread concurrency should be unchanged
239 size_t cpus_after = get_num_cpus();
240 EXPECT_EQ(cpus_after, 8);
241 EXPECT_EQ(cpus_before, cpus_after);
242}
243} // namespace bb
void TearDown() override
void SetUp() override
size_t original_concurrency
Entry point for Barretenberg command-line interface.
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:188
size_t get_num_cpus_pow2()
Definition thread.hpp:25
size_t get_num_cpus()
Definition thread.cpp:33
size_t calculate_num_threads(size_t num_iterations, size_t min_iterations_per_thread)
calculates number of threads to create based on minimum iterations per thread
Definition thread.cpp:238
size_t calculate_num_threads_pow2(size_t num_iterations, size_t min_iterations_per_thread)
calculates number of threads to create based on minimum iterations per thread, guaranteed power of 2
Definition thread.cpp:254
void set_parallel_for_concurrency(size_t num_cores)
Definition thread.cpp:24
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:111
void parallel_for_range(size_t num_points, const std::function< void(size_t, size_t)> &func, size_t no_multhreading_if_less_or_equal)
Split a loop into several loops running in parallel.
Definition thread.cpp:141
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13