tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_Semaphore.tests.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11#if TRACKTION_BENCHMARKS && GRAPH_BENCHMARKS_THREADS
12 #include "../../tracktion_core/utilities/tracktion_Benchmark.h"
13#endif
14
15namespace tracktion { inline namespace graph
16{
17
18#if GRAPH_UNIT_TESTS_SEMAPHORE
19
20class SemaphoreTests : public juce::UnitTest
21{
22public:
23 SemaphoreTests()
24 : juce::UnitTest ("Semaphore", "tracktion_graph") {}
25
26 //==============================================================================
27 void runTest() override
28 {
29 runSemaphoreTests<Semaphore> ("Semaphore");
30 runSemaphoreTests<LightweightSemaphore> ("LightweightSemaphore");
31 }
32
33private:
34 template<typename SemaphoreType>
35 void runSemaphoreTests (juce::String semaphoreName)
36 {
37 beginTest (juce::String ("Semaphore basic tests").replace ("Semaphore", semaphoreName));
38 {
39 {
40 SemaphoreType event (1);
41 expect (event.wait()); // Count of 1 doesn't block
42 }
43
44 {
45 SemaphoreType event (2);
46 event.wait();
47 expect (event.timed_wait (100)); // Count of 2 doesn't block
48 // expect (! event.wait()); // 3rd wait would block
49 }
50
51 {
52 SemaphoreType event (2);
53 expect (event.wait());
54 expect (event.wait());
55 expect (! event.try_wait()); // 3rd wait fails
56 expect (! event.timed_wait (100)); // Timed wait fails
57 }
58 }
59
60 beginTest (juce::String ("Semaphore wakeup tests").replace ("Semaphore", semaphoreName));
61 {
62 constexpr int numThreads = 10;
63 std::atomic<int> counter { 0 }, numThreadsRunning { 0 };
64 SemaphoreType event;
65 auto signalTime = std::chrono::steady_clock::now();
66
67 // Start all the threads
69
70 for (int i = 0; i < numThreads; ++i)
71 {
72 threads.emplace_back ([&, signalTime]
73 {
74 ++numThreadsRunning;
75 event.wait();
76
77 auto signalDuration = std::chrono::steady_clock::now() - signalTime;
78 logMessage (juce::String (std::chrono::duration_cast<std::chrono::microseconds> (signalDuration).count()) + "us");
79
80 ++counter;
81 });
82 }
83
84 // Wait until all the threads have started and are waiting on the event
85 for (;;)
86 {
87 if (numThreadsRunning == numThreads)
88 break;
89
91 }
92
93 // Sleep for a few more ms to ensure they're all waiting
95
96 // Signal all the threads to increment the counter
97 signalTime = std::chrono::steady_clock::now();
98 event.signal (numThreads);
99
100 // Wait for the threads to complete
101 for (auto& t : threads)
102 t.join();
103
104 expectEquals (counter.load(), numThreads);
105 }
106 }
107};
108
109static SemaphoreTests semaphoreTests;
110
111#endif
112
113#if TRACKTION_BENCHMARKS && GRAPH_BENCHMARKS_THREADS
114
115//==============================================================================
116//==============================================================================
117class NoOpBenchmarks : public juce::UnitTest
118{
119public:
120 NoOpBenchmarks()
121 : juce::UnitTest ("no_op", "tracktion_benchmarks") {}
122
123 //==============================================================================
124 void runTest() override
125 {
126 Benchmark benchmark (createBenchmarkDescription ("Time", "no_op", "no_op"));
127
128 for (int i = 0; i < 100'000; ++i)
129 {
130 benchmark.start();
131 benchmark.stop();
132 }
133
134 BenchmarkList::getInstance().addResult (benchmark.getResult());
135 }
136};
137
138static NoOpBenchmarks noOpBenchmarks;
139
140//==============================================================================
141//==============================================================================
142class ChronoNowBenchmarks : public juce::UnitTest
143{
144public:
145 ChronoNowBenchmarks()
146 : juce::UnitTest ("steady_clock::now", "tracktion_benchmarks") {}
147
148 //==============================================================================
149 void runTest() override
150 {
151 Benchmark benchmark (createBenchmarkDescription ("Time", "steady_clock::now", "chrono::steady_clock::now"));
152
153 for (int i = 0; i < 100'000; ++i)
154 {
155 benchmark.start();
156 [[ maybe_unused ]] volatile auto now = std::chrono::steady_clock::now();
157 benchmark.stop();
158 }
159
160 BenchmarkList::getInstance().addResult (benchmark.getResult());
161 }
162};
163
164static ChronoNowBenchmarks chronoNowBenchmarks;
165
166//==============================================================================
167//==============================================================================
168class JUCEMsCounterHiRes : public juce::UnitTest
169{
170public:
171 JUCEMsCounterHiRes()
172 : juce::UnitTest ("juce::Time::getMillisecondCounterHiRes()", "tracktion_benchmarks") {}
173
174 //==============================================================================
175 void runTest() override
176 {
177 Benchmark benchmark (createBenchmarkDescription ("Time", "juce ms timer hi-res", "juce::Time::getMillisecondCounterHiRes()"));
178
179 for (int i = 0; i < 100'000; ++i)
180 {
181 benchmark.start();
182 [[ maybe_unused ]] volatile auto now = juce::Time::getMillisecondCounterHiRes();
183 benchmark.stop();
184 }
185
186 BenchmarkList::getInstance().addResult (benchmark.getResult());
187 }
188};
189
190static JUCEMsCounterHiRes juceMsCounterHiRes;
191
192//==============================================================================
193//==============================================================================
194class ThreadSignallingBenchmarks : public juce::UnitTest
195{
196public:
197 ThreadSignallingBenchmarks()
198 : juce::UnitTest ("Thread signalling", "tracktion_benchmarks") {}
199
200 //==============================================================================
201 void runTest() override
202 {
203 runConditionVariableBenchmarks();
204 runSemaphoreBenchmarks<Semaphore> ("Semaphore");
205 runSemaphoreBenchmarks<LightweightSemaphore> ("LightweightSemaphore");
206
207 runNonWaitingConditionVariableBenchmarks();
208 runNonWaitingSemaphoreBenchmarks<Semaphore> ("Semaphore");
209 runNonWaitingSemaphoreBenchmarks<LightweightSemaphore> ("LightweightSemaphore");
210 }
211
212private:
213 template<typename SemaphoreType>
214 void runSemaphoreBenchmarks (juce::String semaphoreName)
215 {
216 constexpr int numThreads = 10;
217 Benchmark benchmark (createBenchmarkDescription ("Threads",
218 juce::String ("Semaphore signal").replace ("Semaphore", semaphoreName).toStdString(),
219 juce::String ("Signal numThreads from waiting").replace ("numThreads", juce::String (numThreads)).toStdString()));
220
221 using namespace std::literals;
222 std::atomic<int> numThreadsRunning { 0 };
223 SemaphoreType event;
224 auto signalTime = std::chrono::steady_clock::now();
225
226 // Start all the threads
228
229 for (int i = 0; i < numThreads; ++i)
230 {
231 threads.emplace_back ([&]
232 {
233 ++numThreadsRunning;
234 event.wait();
235 });
236 }
237
238 // Wait until all the threads have started and are waiting on the event
239 for (;;)
240 {
241 if (numThreadsRunning == numThreads)
242 break;
243
245 }
246
247 // Sleep for a few more ms to ensure they're all waiting
249
250 // Signal all the threads
251 signalTime = std::chrono::steady_clock::now();
252
253 {
254 // Signal all the threads
255 signalTime = std::chrono::steady_clock::now();
256
257 benchmark.start();
258 event.signal (numThreads);
259 benchmark.stop();
260 }
261
262 // Wait for the threads to complete
263 for (auto& t : threads)
264 t.join();
265
266 BenchmarkList::getInstance().addResult (benchmark.getResult());
267 }
268
269 void runConditionVariableBenchmarks()
270 {
271 constexpr int numThreads = 10;
272 Benchmark benchmark (createBenchmarkDescription ("Threads",
273 juce::String ("CV signal").toStdString(),
274 juce::String ("Signal numThreads from waiting").replace ("numThreads", juce::String (numThreads)).toStdString()));
275
276 using namespace std::literals;
277 std::atomic<int> numThreadsRunning { 0 };
278 juce::WaitableEvent event (true);
279
280 // Start all the threads
282
283 for (int i = 0; i < numThreads; ++i)
284 {
285 threads.emplace_back ([&]
286 {
287 ++numThreadsRunning;
288 event.wait (-1);
289 });
290 }
291
292 // Wait until all the threads have started and are waiting on the event
293 for (;;)
294 {
295 if (numThreadsRunning == numThreads)
296 break;
297
299 }
300
301 // Sleep for a few more ms to ensure they're all waiting
303
304 {
305 benchmark.start();
306
307 // Signal all the threads
308 event.signal();
309
310 benchmark.stop();
311 }
312
313 // Wait for the threads to complete
314 for (auto& t : threads)
315 t.join();
316
317 BenchmarkList::getInstance().addResult (benchmark.getResult());
318 }
319
320 template<typename SemaphoreType>
321 void runNonWaitingSemaphoreBenchmarks (juce::String semaphoreName)
322 {
323 constexpr int numThreads = 10;
324 using namespace std::literals;
325 Benchmark benchmark (createBenchmarkDescription ("Threads",
326 juce::String ("Semaphore signal").replace ("Semaphore", semaphoreName).toStdString(),
327 juce::String ("Signal numThreads (may not be waiting)").replace ("numThreads", juce::String (numThreads)).toStdString()));
328
329 std::atomic<int> numThreadsRunning { 0 };
330 std::atomic<bool> threadShouldExit { false };
331 SemaphoreType event;
332
333 // Start all the threads
335
336 for (int i = 0; i < numThreads; ++i)
337 {
338 threads.emplace_back ([&]
339 {
340 ++numThreadsRunning;
341
342 while (! threadShouldExit)
343 event.wait();
344
345 --numThreadsRunning;
346 });
347 }
348
349 // Wait until all the threads have started and are waiting on the event
350 for (;;)
351 {
352 if (numThreadsRunning == numThreads)
353 break;
354
356 }
357
358 // Sleep for a few more ms to ensure they're all waiting
360
361 for (int i = 0; i < 100'000; ++i)
362 {
363 benchmark.start();
364 event.signal (numThreads);
365 benchmark.stop();
366 }
367
368 // Wait for the threads to complete
369 threadShouldExit = true;
370
371 while (numThreadsRunning > 0)
372 event.signal (numThreads);
373
374 for (auto& t : threads)
375 t.join();
376
377 BenchmarkList::getInstance().addResult (benchmark.getResult());
378 }
379
380 void runNonWaitingConditionVariableBenchmarks()
381 {
382 constexpr int numThreads = 10;
383 using namespace std::literals;
384 Benchmark benchmark (createBenchmarkDescription ("Threads",
385 juce::String ("CV signal").toStdString(),
386 juce::String ("Signal numThreads (may not be waiting)").replace ("numThreads", juce::String (numThreads)).toStdString()));
387
388 std::atomic<int> numThreadsRunning { 0 };
389 std::atomic<bool> threadShouldExit { false };
391
392 // Start all the threads
394
395 for (int i = 0; i < numThreads; ++i)
396 {
397 threads.emplace_back ([&]
398 {
399 ++numThreadsRunning;
400
401 while (! threadShouldExit)
402 event.wait();
403
404 --numThreadsRunning;
405 });
406 }
407
408 // Wait until all the threads have started and are waiting on the event
409 for (;;)
410 {
411 if (numThreadsRunning == numThreads)
412 break;
413
415 }
416
417 // Sleep for a few more ms to ensure they're all waiting
419
420 for (int i = 0; i < 100'000; ++i)
421 {
422 benchmark.start();
423 event.signal();
424 benchmark.stop();
425 }
426
427 // Wait for the threads to complete
428 threadShouldExit = true;
429
430 while (numThreadsRunning > 0)
431 event.signal();
432
433 for (auto& t : threads)
434 t.join();
435
436 BenchmarkList::getInstance().addResult (benchmark.getResult());
437 }
438};
439
440static ThreadSignallingBenchmarks threadSignallingBenchmarks;
441#endif
442
443}} // namespace tracktion_engine
static double getMillisecondCounterHiRes() noexcept
bool wait(double timeOutMilliseconds=-1.0) const
T count(T... args)
T emplace_back(T... args)
T sleep_for(T... args)