| 131 | |
| 132 | template <std::integral Int, std::invocable<Int> F> |
| 133 | Task<void> parallelFor(Int start, Int end, size_t approxCost, F body, int priority) { |
| 134 | const Int range = end - start; |
| 135 | const Int n = nTasks(start, end, approxCost); |
| 136 | |
| 137 | std::vector<Task<void>> tasks; |
| 138 | |
| 139 | { |
| 140 | // The task queue is already thread-safe due to the lock in enqueueTask, but we can optimize a bit by locking only once for all |
| 141 | // tasks. |
| 142 | const std::scoped_lock lock{mTaskQueueMutex}; |
| 143 | |
| 144 | for (Int i = 0; i < n; ++i) { |
| 145 | Int taskStart = start + (range * i / n); |
| 146 | Int taskEnd = start + (range * (i + 1) / n); |
| 147 | TEV_ASSERT(taskStart != taskEnd, "Should not produce tasks with empty range."); |
| 148 | |
| 149 | tasks.emplace_back(enqueueCoroutine( |
| 150 | [taskStart, taskEnd, &body]() -> Task<void> { |
| 151 | for (Int j = taskStart; j < taskEnd; ++j) { |
| 152 | if constexpr (is_coroutine_callable_v<F, Int>) { |
| 153 | co_await body(j); |
| 154 | } else { |
| 155 | body(j); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | co_return; // Make sure this is a coroutine even if body() is not a coroutine |
| 160 | }, |
| 161 | priority |
| 162 | )); |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | co_await awaitAll(tasks); |
| 167 | } |
| 168 | |
| 169 | size_t numThreads() const { return mNumThreads; } |
| 170 |
no test coverage detected