Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/unittest/src/utils/threadpool/ThreadPoolTest.cpp
169684 views
1
/****************************************************************************/
2
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
// Copyright (C) 2020-2025 German Aerospace Center (DLR) and others.
4
// This program and the accompanying materials are made available under the
5
// terms of the Eclipse Public License 2.0 which is available at
6
// https://www.eclipse.org/legal/epl-2.0/
7
// This Source Code may also be made available under the following Secondary
8
// Licenses when the conditions for such availability set forth in the Eclipse
9
// Public License 2.0 are satisfied: GNU General Public License, version 2
10
// or later which is available at
11
// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
/****************************************************************************/
14
/// @file ThreadPoolTest.cpp
15
/// @author Michael Behrisch
16
/// @date 2020-09-09
17
///
18
// Testing the threadpool implementation,
19
// based on https://github.com/vukis/Cpp-Utilities/tree/master/ThreadPool
20
/****************************************************************************/
21
#include <config.h>
22
23
#include <string>
24
#include <vector>
25
#include <chrono>
26
#include <iostream>
27
#include <sstream>
28
#include <iomanip>
29
#include <numeric>
30
31
#include <utils/common/StopWatch.h>
32
#include <utils/threadpool/WorkStealingThreadPool.h>
33
34
35
inline void LoadCPUForRandomTime() {
36
// Sleeping the thread isn't good as it doesn't tie up the
37
// CPU resource in the same way as actual work on a thread would do,
38
// The OS is free to schedule work on the CPU while the thread is
39
// sleeping. Hence we do some busy work. Note that volatile keyword
40
// is necessary to prevent compiler from removing the below code.
41
42
srand(0); // random sequences should be identical
43
44
volatile auto delay = rand() % static_cast<int>(1e5);
45
while (delay != 0) {
46
delay--;
47
}
48
}
49
50
template<typename DurationT>
51
void LoadCPUFor(DurationT&& duration) {
52
for (auto start = std::chrono::steady_clock::now(), now = start;
53
now < start + duration;
54
now = std::chrono::steady_clock::now()) {
55
}
56
}
57
58
#define DO_BENCHMARK_TEST_WITH_DESCRIPTION(description, repeatTimes, test, pool) { \
59
std::cout << " - Benchmark test ( " << #test << ", description: " << description; \
60
StopWatch<> stopWatch; \
61
for (size_t n = 0; n < repeatTimes; ++n) { \
62
stopWatch.start(); \
63
test(pool); \
64
stopWatch.stop(); \
65
} \
66
std::cout << " ) => " << stopWatch.getAverage() << " ms" << std::endl; \
67
}
68
69
#define TEST_ASSERT(expr) \
70
if (!(expr)) { \
71
std::ostringstream ss; \
72
ss << __FILE__ << ":" <<__LINE__ << " " << #expr; \
73
throw std::runtime_error(ss.str()); \
74
}
75
76
#define DO_TEST(test, pool) { \
77
std::cout << " - Test ( " << #test << " " << #pool; \
78
try \
79
{ \
80
test(pool); \
81
} \
82
catch (const std::exception& e) \
83
{ \
84
std::cout << " => failed with: " << e.what() << " )" << std::endl; \
85
throw; \
86
} \
87
std::cout << " => succeed )" << std::endl; \
88
}
89
90
91
void Test_TaskResultIsAsExpected(WorkStealingThreadPool<>& taskSystem) {
92
constexpr size_t taskCount = 10000;
93
94
std::vector<std::future<size_t>> results;
95
96
for (size_t i = 0; i < taskCount; ++i)
97
results.push_back(taskSystem.executeAsync([i](int) {
98
return i * i;
99
}));
100
101
for (size_t i = 0; i < taskCount; ++i) {
102
TEST_ASSERT(i * i == results[i].get());
103
}
104
}
105
106
void Test_RandomTaskExecutionTime(WorkStealingThreadPool<>& taskSystem) {
107
constexpr size_t taskCount = 10000;
108
109
std::vector<std::future<void>> results;
110
111
for (size_t i = 0; i < taskCount; ++i)
112
results.push_back(taskSystem.executeAsync([](int) {
113
LoadCPUForRandomTime();
114
}));
115
116
for (auto& result : results) {
117
result.wait();
118
}
119
}
120
121
void Test_1nsTaskExecutionTime(WorkStealingThreadPool<>& taskSystem) {
122
constexpr size_t taskCount = 10000;
123
std::vector<std::future<void>> results;
124
125
for (size_t i = 0; i < taskCount; ++i)
126
results.push_back(taskSystem.executeAsync([](int) {
127
LoadCPUFor(std::chrono::nanoseconds(1));
128
}));
129
130
for (auto& result : results) {
131
result.wait();
132
}
133
}
134
135
void Test_100msTaskExecutionTime(WorkStealingThreadPool<>& taskSystem) {
136
constexpr size_t taskCount = 10;
137
std::vector<std::future<void>> results;
138
139
for (size_t i = 0; i < taskCount; ++i)
140
results.push_back(taskSystem.executeAsync([](int) {
141
LoadCPUFor(std::chrono::milliseconds(100));
142
}));
143
144
for (auto& result : results) {
145
result.wait();
146
}
147
}
148
149
void Test_EmptyTask(WorkStealingThreadPool<>& taskSystem) {
150
constexpr size_t taskCount = 10000;
151
152
std::vector<std::future<void>> results;
153
154
for (size_t i = 0; i < taskCount; ++i)
155
results.push_back(taskSystem.executeAsync([](int) {}));
156
157
for (auto& result : results) {
158
result.wait();
159
}
160
}
161
162
template<class TaskT>
163
void RepeatTask(WorkStealingThreadPool<>& taskSystem, TaskT&& task, size_t times) {
164
std::vector<std::future<void>> results;
165
166
// Here we need not to std::forward just copy task.
167
// Because if the universal reference of task has bound to an r-value reference
168
// then std::forward will have the same effect as std::move and thus task is not required to contain a valid task.
169
// Universal reference must only be std::forward'ed a exactly zero or one times.
170
for (size_t i = 0; i < times; ++i) {
171
results.push_back(taskSystem.executeAsync(task));
172
}
173
174
for (auto& result : results) {
175
result.wait();
176
}
177
}
178
179
void Test_MultipleTaskProducers(WorkStealingThreadPool<>& taskSystem) {
180
constexpr size_t taskCount = 1000;
181
182
std::vector<std::thread> taskProducers{ std::max(1u, std::thread::hardware_concurrency()) };
183
184
for (auto& producer : taskProducers)
185
producer = std::thread([&] { RepeatTask(taskSystem, [](int) {
186
LoadCPUForRandomTime();
187
}, taskCount);
188
});
189
190
for (auto& producer : taskProducers) {
191
if (producer.joinable()) {
192
producer.join();
193
}
194
}
195
}
196
197
int main() {
198
std::vector<int> pseudoContext(std::thread::hardware_concurrency(), 0);
199
WorkStealingThreadPool<int> stealingTaskSystem(true, pseudoContext);
200
WorkStealingThreadPool<int> multiQueueTaskSystem(false, pseudoContext);
201
202
std::cout << "==========================================" << std::endl;
203
std::cout << " FUNCTIONAL TESTS " << std::endl;
204
std::cout << "==========================================" << std::endl;
205
DO_TEST(Test_TaskResultIsAsExpected, multiQueueTaskSystem);
206
DO_TEST(Test_TaskResultIsAsExpected, stealingTaskSystem);
207
std::cout << std::endl;
208
209
std::cout << "==========================================" << std::endl;
210
std::cout << " PERFORMANCE TESTS " << std::endl;
211
std::cout << "==========================================" << std::endl;
212
std::cout << "Number of cores: " << std::thread::hardware_concurrency() << std::endl;
213
constexpr size_t NumOfRuns = 10;
214
std::cout << std::endl;
215
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_RandomTaskExecutionTime, multiQueueTaskSystem);
216
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_RandomTaskExecutionTime, stealingTaskSystem);
217
std::cout << std::endl;
218
219
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_1nsTaskExecutionTime, multiQueueTaskSystem);
220
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_1nsTaskExecutionTime, stealingTaskSystem);
221
std::cout << std::endl;
222
223
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_100msTaskExecutionTime, multiQueueTaskSystem);
224
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_100msTaskExecutionTime, stealingTaskSystem);
225
std::cout << std::endl;
226
227
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_EmptyTask, multiQueueTaskSystem);
228
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_EmptyTask, stealingTaskSystem);
229
std::cout << std::endl;
230
231
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_MultipleTaskProducers, multiQueueTaskSystem);
232
DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_MultipleTaskProducers, stealingTaskSystem);
233
std::cout << std::endl;
234
235
return 0;
236
}
237
238