Path: blob/main/unittest/src/utils/threadpool/ThreadPoolTest.cpp
169684 views
/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2020-2025 German Aerospace Center (DLR) and others.3// This program and the accompanying materials are made available under the4// terms of the Eclipse Public License 2.0 which is available at5// https://www.eclipse.org/legal/epl-2.0/6// This Source Code may also be made available under the following Secondary7// Licenses when the conditions for such availability set forth in the Eclipse8// Public License 2.0 are satisfied: GNU General Public License, version 29// or later which is available at10// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html11// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later12/****************************************************************************/13/// @file ThreadPoolTest.cpp14/// @author Michael Behrisch15/// @date 2020-09-0916///17// Testing the threadpool implementation,18// based on https://github.com/vukis/Cpp-Utilities/tree/master/ThreadPool19/****************************************************************************/20#include <config.h>2122#include <string>23#include <vector>24#include <chrono>25#include <iostream>26#include <sstream>27#include <iomanip>28#include <numeric>2930#include <utils/common/StopWatch.h>31#include <utils/threadpool/WorkStealingThreadPool.h>323334inline void LoadCPUForRandomTime() {35// Sleeping the thread isn't good as it doesn't tie up the36// CPU resource in the same way as actual work on a thread would do,37// The OS is free to schedule work on the CPU while the thread is38// sleeping. Hence we do some busy work. Note that volatile keyword39// is necessary to prevent compiler from removing the below code.4041srand(0); // random sequences should be identical4243volatile auto delay = rand() % static_cast<int>(1e5);44while (delay != 0) {45delay--;46}47}4849template<typename DurationT>50void LoadCPUFor(DurationT&& duration) {51for (auto start = std::chrono::steady_clock::now(), now = start;52now < start + duration;53now = std::chrono::steady_clock::now()) {54}55}5657#define DO_BENCHMARK_TEST_WITH_DESCRIPTION(description, repeatTimes, test, pool) { \58std::cout << " - Benchmark test ( " << #test << ", description: " << description; \59StopWatch<> stopWatch; \60for (size_t n = 0; n < repeatTimes; ++n) { \61stopWatch.start(); \62test(pool); \63stopWatch.stop(); \64} \65std::cout << " ) => " << stopWatch.getAverage() << " ms" << std::endl; \66}6768#define TEST_ASSERT(expr) \69if (!(expr)) { \70std::ostringstream ss; \71ss << __FILE__ << ":" <<__LINE__ << " " << #expr; \72throw std::runtime_error(ss.str()); \73}7475#define DO_TEST(test, pool) { \76std::cout << " - Test ( " << #test << " " << #pool; \77try \78{ \79test(pool); \80} \81catch (const std::exception& e) \82{ \83std::cout << " => failed with: " << e.what() << " )" << std::endl; \84throw; \85} \86std::cout << " => succeed )" << std::endl; \87}888990void Test_TaskResultIsAsExpected(WorkStealingThreadPool<>& taskSystem) {91constexpr size_t taskCount = 10000;9293std::vector<std::future<size_t>> results;9495for (size_t i = 0; i < taskCount; ++i)96results.push_back(taskSystem.executeAsync([i](int) {97return i * i;98}));99100for (size_t i = 0; i < taskCount; ++i) {101TEST_ASSERT(i * i == results[i].get());102}103}104105void Test_RandomTaskExecutionTime(WorkStealingThreadPool<>& taskSystem) {106constexpr size_t taskCount = 10000;107108std::vector<std::future<void>> results;109110for (size_t i = 0; i < taskCount; ++i)111results.push_back(taskSystem.executeAsync([](int) {112LoadCPUForRandomTime();113}));114115for (auto& result : results) {116result.wait();117}118}119120void Test_1nsTaskExecutionTime(WorkStealingThreadPool<>& taskSystem) {121constexpr size_t taskCount = 10000;122std::vector<std::future<void>> results;123124for (size_t i = 0; i < taskCount; ++i)125results.push_back(taskSystem.executeAsync([](int) {126LoadCPUFor(std::chrono::nanoseconds(1));127}));128129for (auto& result : results) {130result.wait();131}132}133134void Test_100msTaskExecutionTime(WorkStealingThreadPool<>& taskSystem) {135constexpr size_t taskCount = 10;136std::vector<std::future<void>> results;137138for (size_t i = 0; i < taskCount; ++i)139results.push_back(taskSystem.executeAsync([](int) {140LoadCPUFor(std::chrono::milliseconds(100));141}));142143for (auto& result : results) {144result.wait();145}146}147148void Test_EmptyTask(WorkStealingThreadPool<>& taskSystem) {149constexpr size_t taskCount = 10000;150151std::vector<std::future<void>> results;152153for (size_t i = 0; i < taskCount; ++i)154results.push_back(taskSystem.executeAsync([](int) {}));155156for (auto& result : results) {157result.wait();158}159}160161template<class TaskT>162void RepeatTask(WorkStealingThreadPool<>& taskSystem, TaskT&& task, size_t times) {163std::vector<std::future<void>> results;164165// Here we need not to std::forward just copy task.166// Because if the universal reference of task has bound to an r-value reference167// then std::forward will have the same effect as std::move and thus task is not required to contain a valid task.168// Universal reference must only be std::forward'ed a exactly zero or one times.169for (size_t i = 0; i < times; ++i) {170results.push_back(taskSystem.executeAsync(task));171}172173for (auto& result : results) {174result.wait();175}176}177178void Test_MultipleTaskProducers(WorkStealingThreadPool<>& taskSystem) {179constexpr size_t taskCount = 1000;180181std::vector<std::thread> taskProducers{ std::max(1u, std::thread::hardware_concurrency()) };182183for (auto& producer : taskProducers)184producer = std::thread([&] { RepeatTask(taskSystem, [](int) {185LoadCPUForRandomTime();186}, taskCount);187});188189for (auto& producer : taskProducers) {190if (producer.joinable()) {191producer.join();192}193}194}195196int main() {197std::vector<int> pseudoContext(std::thread::hardware_concurrency(), 0);198WorkStealingThreadPool<int> stealingTaskSystem(true, pseudoContext);199WorkStealingThreadPool<int> multiQueueTaskSystem(false, pseudoContext);200201std::cout << "==========================================" << std::endl;202std::cout << " FUNCTIONAL TESTS " << std::endl;203std::cout << "==========================================" << std::endl;204DO_TEST(Test_TaskResultIsAsExpected, multiQueueTaskSystem);205DO_TEST(Test_TaskResultIsAsExpected, stealingTaskSystem);206std::cout << std::endl;207208std::cout << "==========================================" << std::endl;209std::cout << " PERFORMANCE TESTS " << std::endl;210std::cout << "==========================================" << std::endl;211std::cout << "Number of cores: " << std::thread::hardware_concurrency() << std::endl;212constexpr size_t NumOfRuns = 10;213std::cout << std::endl;214DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_RandomTaskExecutionTime, multiQueueTaskSystem);215DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_RandomTaskExecutionTime, stealingTaskSystem);216std::cout << std::endl;217218DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_1nsTaskExecutionTime, multiQueueTaskSystem);219DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_1nsTaskExecutionTime, stealingTaskSystem);220std::cout << std::endl;221222DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_100msTaskExecutionTime, multiQueueTaskSystem);223DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_100msTaskExecutionTime, stealingTaskSystem);224std::cout << std::endl;225226DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_EmptyTask, multiQueueTaskSystem);227DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_EmptyTask, stealingTaskSystem);228std::cout << std::endl;229230DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on multiple task queues", NumOfRuns, Test_MultipleTaskProducers, multiQueueTaskSystem);231DO_BENCHMARK_TEST_WITH_DESCRIPTION("thread pool based on work stealing queue ", NumOfRuns, Test_MultipleTaskProducers, stealingTaskSystem);232std::cout << std::endl;233234return 0;235}236237238