blob: 8d085df42fb9a2938abf6c3c84751fd714566872 [file] [log] [blame]
/* Parallel for loops
Copyright (C) 2019-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/>. */
#ifndef GDBSUPPORT_PARALLEL_FOR_H
#define GDBSUPPORT_PARALLEL_FOR_H
#include <algorithm>
#include <atomic>
#include <tuple>
#include "gdbsupport/thread-pool.h"
namespace gdb
{
/* A "parallel-for" implementation using a shared work queue. Work items get
popped in batches of size up to BATCH_SIZE from the queue and handed out to
worker threads.
Each worker thread instantiates an object of type Worker, forwarding ARGS to
its constructor. The Worker object can be used to keep some per-worker
thread state.
Worker threads call Worker::operator() repeatedly until the queue is
empty. */
template<std::size_t batch_size, class RandomIt, class Worker,
class... WorkerArgs>
void
parallel_for_each (const RandomIt first, const RandomIt last,
WorkerArgs &&...worker_args)
{
/* If enabled, print debug info about how the work is distributed across
the threads. */
const bool parallel_for_each_debug = false;
gdb_assert (first <= last);
if (parallel_for_each_debug)
{
debug_printf ("Parallel for: n elements: %zu\n",
static_cast<std::size_t> (last - first));
debug_printf ("Parallel for: batch size: %zu\n", batch_size);
}
const size_t n_worker_threads
= std::max<size_t> (thread_pool::g_thread_pool->thread_count (), 1);
std::vector<gdb::future<void>> results;
/* The next item to hand out. */
std::atomic<RandomIt> next = first;
/* The worker thread task.
We need to capture args as a tuple, because it's not possible to capture
the parameter pack directly in C++17. Once we migrate to C++20, the
capture can be simplified to:
... args = std::forward<Args>(args)
and `args` can be used as-is in the lambda. */
auto args_tuple
= std::forward_as_tuple (std::forward<WorkerArgs> (worker_args)...);
auto task = [&next, first, last, n_worker_threads, &args_tuple] ()
{
/* Instantiate the user-defined worker. */
auto worker = std::make_from_tuple<Worker> (args_tuple);
for (;;)
{
/* Grab a snapshot of NEXT. */
auto local_next = next.load ();
gdb_assert (local_next <= last);
/* Number of remaining items. */
auto n_remaining = last - local_next;
gdb_assert (n_remaining >= 0);
/* Are we done? */
if (n_remaining == 0)
break;
const auto this_batch_size
= std::min<std::size_t> (batch_size, n_remaining);
/* The range to process in this iteration. */
const auto this_batch_first = local_next;
const auto this_batch_last = local_next + this_batch_size;
/* Update NEXT. If the current value of NEXT doesn't match
LOCAL_NEXT, it means another thread updated it concurrently,
restart. */
if (!next.compare_exchange_weak (local_next, this_batch_last))
continue;
if (parallel_for_each_debug)
debug_printf ("Processing %zu items, range [%zu, %zu[\n",
this_batch_size,
static_cast<size_t> (this_batch_first - first),
static_cast<size_t> (this_batch_last - first));
worker (this_batch_first, this_batch_last);
}
};
/* Start N_WORKER_THREADS tasks. */
for (int i = 0; i < n_worker_threads; ++i)
results.push_back (gdb::thread_pool::g_thread_pool->post_task (task));
/* Wait for all of them to be finished. */
for (auto &fut : results)
fut.get ();
}
/* A sequential drop-in replacement of parallel_for_each. This can be useful
when debugging multi-threading behavior, and you want to limit
multi-threading in a fine-grained way. */
template<class RandomIt, class Worker, class... WorkerArgs>
void
sequential_for_each (RandomIt first, RandomIt last, WorkerArgs &&...worker_args)
{
if (first == last)
return;
Worker (std::forward<WorkerArgs> (worker_args)...) (first, last);
}
}
#endif /* GDBSUPPORT_PARALLEL_FOR_H */