| /* Self tests for parallel_for_each |
| |
| Copyright (C) 2021-2025 Free Software Foundation, Inc. |
| |
| This file is part of GDB. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "gdbsupport/selftest.h" |
| #include "gdbsupport/parallel-for.h" |
| |
| #if CXX_STD_THREAD |
| |
| #include "gdbsupport/thread-pool.h" |
| |
| namespace selftests { |
| namespace parallel_for { |
| |
| struct save_restore_n_threads |
| { |
| save_restore_n_threads () |
| : n_threads (gdb::thread_pool::g_thread_pool->thread_count ()) |
| { |
| } |
| |
| ~save_restore_n_threads () |
| { |
| gdb::thread_pool::g_thread_pool->set_thread_count (n_threads); |
| } |
| |
| int n_threads; |
| }; |
| |
| using foreach_callback_t = gdb::function_view<void (iterator_range<int *> range)>; |
| using do_foreach_t = gdb::function_view<void (int *first, int *last, |
| foreach_callback_t)>; |
| |
| /* Run one parallel-for-each test on the range [1, UPPER_BOUND) using the |
| parallel-for-each implementation DO_FOREACH. */ |
| |
| static void |
| test_one (do_foreach_t do_foreach, int upper_bound) |
| { |
| std::vector<int> input; |
| |
| for (int i = 0; i < upper_bound; ++i) |
| input.emplace_back (i); |
| |
| std::vector<int> output; |
| std::mutex mtx; |
| |
| /* The (unfortunate) reason why we don't use std::vector<int>::iterator as |
| the parallel-for-each iterator type is that std::atomic won't work with |
| that type when building with -D_GLIBCXX_DEBUG. */ |
| do_foreach (input.data (), input.data () + input.size (), |
| [&] (iterator_range<int *> range) |
| { |
| /* We shouldn't receive empty ranges. */ |
| SELF_CHECK (!range.empty ()); |
| |
| std::lock_guard lock (mtx); |
| |
| for (int i : range) |
| output.emplace_back (i * 2); |
| }); |
| |
| /* Verify that each item was processed exactly once. */ |
| SELF_CHECK (output.size () == upper_bound); |
| std::sort (output.begin (), output.end ()); |
| |
| for (int i = 0; i < output.size (); ++i) |
| SELF_CHECK (output[i] == i * 2); |
| } |
| |
| /* Run all tests on the parallel-for-each implementation DO_FOREACH. */ |
| |
| static void |
| test_one_function (int n_threads, do_foreach_t do_foreach) |
| { |
| save_restore_n_threads saver; |
| gdb::thread_pool::g_thread_pool->set_thread_count (n_threads); |
| |
| /* Test with a few arbitrary number of items. */ |
| test_one (do_foreach, 0); |
| test_one (do_foreach, 1); |
| test_one (do_foreach, 1000); |
| } |
| |
| static void |
| test_parallel_for_each () |
| { |
| struct test_worker |
| { |
| /* DUMMY is there to test passing multiple arguments to the worker |
| constructor. */ |
| test_worker (foreach_callback_t callback, int dummy) |
| : m_callback (callback) |
| { |
| } |
| |
| void operator() (iterator_range<int *> range) |
| { |
| return m_callback (range); |
| } |
| |
| private: |
| foreach_callback_t m_callback; |
| }; |
| |
| const std::vector<do_foreach_t> for_each_functions |
| { |
| /* Test gdb::parallel_for_each. */ |
| [] (int *start, int *end, foreach_callback_t callback) |
| { gdb::parallel_for_each<1, int *, test_worker> (start, end, callback, 0); }, |
| |
| /* Test gdb::parallel_for_each_async. */ |
| [] (int *start, int *end, foreach_callback_t callback) |
| { |
| bool done_flag = false; |
| std::condition_variable cv; |
| std::mutex mtx; |
| |
| gdb::parallel_for_each_async<1, int *, test_worker> (start, end, |
| [&mtx, &done_flag, &cv] () |
| { |
| std::lock_guard<std::mutex> lock (mtx); |
| done_flag = true; |
| cv.notify_one(); |
| }, callback, 0); |
| |
| /* Wait for the async parallel-for to complete. */ |
| std::unique_lock<std::mutex> lock (mtx); |
| cv.wait (lock, [&done_flag] () { return done_flag; }); |
| }, |
| |
| /* Test gdb::sequential_for_each. */ |
| [] (int *start, int *end, foreach_callback_t callback) |
| { gdb::sequential_for_each<int *, test_worker> (start, end, callback, 0); }, |
| }; |
| |
| int default_thread_count = gdb::thread_pool::g_thread_pool->thread_count (); |
| |
| for (int n_threads : { 0, 1, 3, default_thread_count }) |
| for (const auto &for_each_function : for_each_functions) |
| test_one_function (n_threads, for_each_function); |
| } |
| |
| } /* namespace parallel_for */ |
| } /* namespace selftests */ |
| |
| #endif /* CXX_STD_THREAD */ |
| |
| INIT_GDB_FILE (parallel_for_selftests) |
| { |
| #ifdef CXX_STD_THREAD |
| selftests::register_test ("parallel_for", |
| selftests::parallel_for::test_parallel_for_each); |
| #endif /* CXX_STD_THREAD */ |
| } |