Blender V5.0
task_pool.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11#include <atomic>
12#include <cstdlib>
13#include <memory>
14#include <utility>
15
16#include "MEM_guardedalloc.h"
17
18#include "DNA_listBase.h"
19
20#include "BLI_assert.h"
21#include "BLI_mempool.h"
22#include "BLI_task.h"
23#include "BLI_threads.h"
24#include "BLI_vector.hh"
25
26#ifdef WITH_TBB
27# include <tbb/blocked_range.h>
28# include <tbb/task_arena.h>
29# include <tbb/task_group.h>
30#endif
31
37class Task {
38 public:
41 void *taskdata;
44
53
55 {
56 if (free_taskdata) {
57 if (freedata) {
59 }
60 else {
62 }
63 }
64 }
65
66 /* Move constructor.
67 * For performance, ensure we never copy the task and only move it.
68 * For TBB version 2017 and earlier we apply a workaround to make up for
69 * the lack of move constructor support. */
70 Task(Task &&other)
71 : pool(other.pool),
72 run(other.run),
73 taskdata(other.taskdata),
75 freedata(other.freedata)
76 {
77 other.pool = nullptr;
78 other.run = nullptr;
79 other.taskdata = nullptr;
80 other.free_taskdata = false;
81 other.freedata = nullptr;
82 }
83
84/* TBB has a check in `tbb/include/task_group.h` where `__TBB_CPP11_RVALUE_REF_PRESENT` should
85 * evaluate to true as with the other MSVC build. However, because of the clang compiler
86 * it does not and we attempt to call a deleted constructor in the tbb_task_pool_run function.
87 * This check fixes this issue and keeps our Task constructor valid. */
88#if (defined(WITH_TBB) && TBB_INTERFACE_VERSION_MAJOR < 10) || \
89 (defined(_MSC_VER) && defined(__clang__) && TBB_INTERFACE_VERSION_MAJOR < 12)
90 Task(const Task &other)
91 : pool(other.pool),
92 run(other.run),
93 taskdata(other.taskdata),
95 freedata(other.freedata)
96 {
97 ((Task &)other).pool = nullptr;
98 ((Task &)other).run = nullptr;
99 ((Task &)other).taskdata = nullptr;
100 ((Task &)other).free_taskdata = false;
101 ((Task &)other).freedata = nullptr;
102 }
103#else
104 Task(const Task &other) = delete;
105#endif
106
107 Task &operator=(const Task &other) = delete;
108 Task &operator=(Task &&other) = delete;
109
110 void operator()() const;
111};
112
113/* Execute task. */
115{
116 run(pool, taskdata);
117}
118
119/* TBB Task Group.
120 *
121 * Subclass since there seems to be no other way to set priority. */
122
123#ifdef WITH_TBB
124class TBBTaskGroup : public tbb::task_group {
125 public:
126 TBBTaskGroup(eTaskPriority priority)
127 {
128# if TBB_INTERFACE_VERSION_MAJOR >= 12
129 /* TODO: support priorities in TBB 2021, where they are only available as
130 * part of task arenas, no longer for task groups. Or remove support for
131 * task priorities if they are no longer useful. */
132 UNUSED_VARS(priority);
133# else
134 switch (priority) {
136 my_context.set_priority(tbb::priority_low);
137 break;
139 my_context.set_priority(tbb::priority_normal);
140 break;
141 }
142# endif
143 }
144};
145#endif
146
147/* Task Pool */
148
156
157struct TaskPool {
160
161 void *userdata;
162
163#ifdef WITH_TBB
164 /* TBB task pool. */
165 std::unique_ptr<TBBTaskGroup> tbb_group;
166#endif
167 volatile bool is_suspended = false;
169
170 /* Background task pool. */
173 volatile bool background_is_canceling = false;
174
176
179 {
180 this->use_threads = BLI_task_scheduler_num_threads() > 1 && type != TASK_POOL_NO_THREADS;
181
182 /* Background task pool uses regular TBB scheduling if available. Only when
183 * building without TBB or running with -t 1 do we need to ensure these tasks
184 * do not block the main thread. */
185 if (this->type == TASK_POOL_BACKGROUND && this->use_threads) {
186 this->type = TASK_POOL_TBB;
187 }
188
189 switch (this->type) {
190 case TASK_POOL_TBB:
194 this->is_suspended = true;
195 }
196
197#ifdef WITH_TBB
198 if (use_threads) {
199 this->tbb_group = std::make_unique<TBBTaskGroup>(priority);
200 }
201#endif
202 break;
203 }
206 this->background_queue = BLI_thread_queue_init();
207 BLI_threadpool_init(&this->background_threads, this->background_task_run, 1);
208 break;
209 }
210 }
211 }
212
214 {
215 switch (type) {
216 case TASK_POOL_TBB:
219 break;
222 this->background_task_pool_work_and_wait();
223
224 BLI_threadpool_end(&this->background_threads);
225 BLI_thread_queue_free(this->background_queue);
226 break;
227 }
228 }
229 }
230
231 TaskPool(TaskPool &&other) = delete;
232#if 0
233 : type(other.type), use_threads(other.use_threads), userdata(other.userdata)
234 {
235 other.pool = nullptr;
236 other.run = nullptr;
237 other.taskdata = nullptr;
238 other.free_taskdata = false;
239 other.freedata = nullptr;
240 }
241#endif
242
243 TaskPool(const TaskPool &other) = delete;
244
245 TaskPool &operator=(const TaskPool &other) = delete;
246 TaskPool &operator=(TaskPool &&other) = delete;
247
252 void *taskdata,
253 bool free_taskdata,
254 TaskFreeFunction freedata)
255 {
256 switch (this->type) {
257 case TASK_POOL_TBB:
260 this->tbb_task_pool_run({this, run, taskdata, free_taskdata, freedata});
261 break;
264 this->background_task_pool_run({this, run, taskdata, free_taskdata, freedata});
265 break;
266 }
267 }
268
273 {
274 switch (this->type) {
275 case TASK_POOL_TBB:
278 this->tbb_task_pool_work_and_wait();
279 break;
282 this->background_task_pool_work_and_wait();
283 break;
284 }
285 }
286
290 void cancel()
291 {
292 switch (this->type) {
293 case TASK_POOL_TBB:
296 this->tbb_task_pool_cancel();
297 break;
300 this->background_task_pool_cancel();
301 break;
302 }
303 }
304
306 {
307 switch (this->type) {
308 case TASK_POOL_TBB:
311 return this->tbb_task_pool_canceled();
314 return this->background_task_pool_canceled();
315 }
316 BLI_assert_msg(0, "TaskPool::current_canceled: Control flow should not come here!");
317 return false;
318 }
319
320 private:
321 /* TBB Task Pool.
322 *
323 * Task pool using the TBB scheduler for tasks. When building without TBB
324 * support or running Blender with -t 1, this reverts to single threaded.
325 *
326 * Tasks may be suspended until in all are created, to make it possible to
327 * initialize data structures and create tasks in a single pass. */
328 void tbb_task_pool_run(Task &&task);
329 void tbb_task_pool_work_and_wait();
330 void tbb_task_pool_cancel();
331 bool tbb_task_pool_canceled();
332
333 /* Background Task Pool.
334 *
335 * Fallback for running background tasks when building without TBB. */
336 void background_task_pool_run(Task &&task);
337 void background_task_pool_work_and_wait();
338 void background_task_pool_cancel();
339 bool background_task_pool_canceled();
340 static void *background_task_run(void *userdata);
341};
342
343void TaskPool::tbb_task_pool_run(Task &&task)
344{
346 if (this->is_suspended) {
347 /* Suspended task that will be executed in work_and_wait(). */
348 this->suspended_tasks.append(std::move(task));
349
350 /* Added as part of original 'use TBB' commit (d8a3f3595af0fb). Unclear whether this is still
351 * needed, tests are passing on linux buildbot, but not sure if any would trigger the issue
352 * addressed by this code. So keeping around for now. */
353#if 0
354# ifdef __GNUC__
355 /* Work around apparent compiler bug where task is not properly copied
356 * to task_mem. This appears unrelated to the use of placement new or
357 * move semantics, happens even writing to a plain C struct. Rather the
358 * call into TBB seems to have some indirect effect. */
359 std::atomic_thread_fence(std::memory_order_release);
360# endif
361#endif
362 }
363#ifdef WITH_TBB
364 else if (this->use_threads) {
365 /* Execute in TBB task group. */
366 this->tbb_group->run(std::move(task));
367 }
368#endif
369 else {
370 /* Execute immediately. */
371 task();
372 }
373}
374
375void TaskPool::tbb_task_pool_work_and_wait()
376{
378 /* Start any suspended task now. */
379 if (!this->suspended_tasks.is_empty()) {
381 this->is_suspended = false;
382
383 for (Task &task : this->suspended_tasks) {
384 this->tbb_task_pool_run(std::move(task));
385 }
386 this->suspended_tasks.clear();
387 }
388
389#ifdef WITH_TBB
390 if (this->use_threads) {
391 /* This is called wait(), but internally it can actually do work. This
392 * matters because we don't want recursive usage of task pools to run
393 * out of threads and get stuck. */
394 this->tbb_group->wait();
395 }
396#endif
397}
398
399void TaskPool::tbb_task_pool_cancel()
400{
402#ifdef WITH_TBB
403 if (this->use_threads) {
404 this->tbb_group->cancel();
405 this->tbb_group->wait();
406 }
407#endif
408}
409
410bool TaskPool::tbb_task_pool_canceled()
411{
413#ifdef WITH_TBB
414 if (this->use_threads) {
415 return tbb::is_current_task_group_canceling();
416 }
417#endif
418 return false;
419}
420
421void TaskPool::background_task_pool_run(Task &&task)
422{
424
425 Task *task_mem = MEM_new<Task>(__func__, std::move(task));
427 task_mem,
431
434 }
435}
436
437void TaskPool::background_task_pool_work_and_wait()
438{
440
441 /* Signal background thread to stop waiting for new tasks if none are
442 * left, and wait for tasks and thread to finish. */
446}
447
448void TaskPool::background_task_pool_cancel()
449{
451
452 this->background_is_canceling = true;
453
454 /* Remove tasks not yet started by background thread. */
456 while (Task *task = static_cast<Task *>(BLI_thread_queue_pop(this->background_queue))) {
457 MEM_delete(task);
458 }
459
460 /* Let background thread finish or cancel task it is working on. */
462 this->background_is_canceling = false;
463}
464
465bool TaskPool::background_task_pool_canceled()
466{
468
469 return this->background_is_canceling;
470}
471
472void *TaskPool::background_task_run(void *userdata)
473{
474 TaskPool *pool = static_cast<TaskPool *>(userdata);
475 while (Task *task = static_cast<Task *>(BLI_thread_queue_pop(pool->background_queue))) {
476 (*task)();
477 MEM_delete(task);
478 }
479 return nullptr;
480}
481
482/* Task Pool public API. */
483
485{
486 return MEM_new<TaskPool>(__func__, TASK_POOL_TBB, priority, userdata);
487}
488
490{
491 /* NOTE: In multi-threaded context, there is no differences with #BLI_task_pool_create(),
492 * but in single-threaded case it is ensured to have at least one worker thread to run on
493 * (i.e. you don't have to call #BLI_task_pool_work_and_wait
494 * on it to be sure it will be processed).
495 *
496 * NOTE: Background pools are non-recursive
497 * (that is, you should not create other background pools in tasks assigned to a background pool,
498 * they could end never being executed, since the 'fallback' background thread is already
499 * busy with parent task in single-threaded context). */
500 return MEM_new<TaskPool>(__func__, TASK_POOL_BACKGROUND, priority, userdata);
501}
502
504{
505 /* NOTE: Similar to #BLI_task_pool_create() but does not schedule any tasks for execution
506 * for until BLI_task_pool_work_and_wait() is called. This helps reducing threading
507 * overhead when pushing huge amount of small initial tasks from the main thread. */
508 return MEM_new<TaskPool>(__func__, TASK_POOL_TBB_SUSPENDED, priority, userdata);
509}
510
512{
513 return MEM_new<TaskPool>(__func__, TASK_POOL_NO_THREADS, TASK_PRIORITY_HIGH, userdata);
514}
515
517{
518 return MEM_new<TaskPool>(__func__, TASK_POOL_BACKGROUND_SERIAL, priority, userdata);
519}
520
522{
523 MEM_delete(pool);
524}
525
527 TaskRunFunction run,
528 void *taskdata,
529 bool free_taskdata,
530 TaskFreeFunction freedata)
531{
532 pool->task_push(run, taskdata, free_taskdata, freedata);
533}
534
536{
537 pool->work_and_wait();
538}
539
541{
542 pool->cancel();
543}
544
546{
547 return pool->current_canceled();
548}
549
551{
552 return pool->userdata;
553}
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
int BLI_task_scheduler_num_threads(void)
eTaskPriority
Definition BLI_task.h:51
@ TASK_PRIORITY_LOW
Definition BLI_task.h:52
@ TASK_PRIORITY_HIGH
Definition BLI_task.h:53
void(* TaskRunFunction)(TaskPool *__restrict pool, void *taskdata)
Definition BLI_task.h:57
void(* TaskFreeFunction)(TaskPool *__restrict pool, void *taskdata)
Definition BLI_task.h:58
@ BLI_THREAD_QUEUE_WORK_PRIORITY_HIGH
@ BLI_THREAD_QUEUE_WORK_PRIORITY_NORMAL
void * BLI_thread_queue_pop(ThreadQueue *queue)
Definition threads.cc:712
ThreadQueue * BLI_thread_queue_init(void)
Definition threads.cc:624
void BLI_threadpool_remove(struct ListBase *threadbase, void *callerdata)
Definition threads.cc:197
void BLI_threadpool_init(struct ListBase *threadbase, void *(*do_thread)(void *), int tot)
Definition threads.cc:121
void BLI_thread_queue_free(ThreadQueue *queue)
Definition threads.cc:635
void BLI_threadpool_end(struct ListBase *threadbase)
Definition threads.cc:234
uint64_t BLI_thread_queue_push(ThreadQueue *queue, void *work, ThreadQueueWorkPriority priority)
Definition threads.cc:645
void BLI_thread_queue_nowait(ThreadQueue *queue)
Definition threads.cc:851
void BLI_thread_queue_wait_finish(ThreadQueue *queue)
Definition threads.cc:862
void BLI_threadpool_clear(struct ListBase *threadbase)
Definition threads.cc:223
int BLI_available_threads(struct ListBase *threadbase)
Definition threads.cc:146
void BLI_threadpool_insert(struct ListBase *threadbase, void *callerdata)
Definition threads.cc:184
#define UNUSED_VARS(...)
#define ELEM(...)
These structs are the foundation for all linked lists in the library system.
Read Guarded memory(de)allocation.
void * taskdata
Definition task_pool.cc:41
Task & operator=(const Task &other)=delete
~Task()
Definition task_pool.cc:54
bool free_taskdata
Definition task_pool.cc:42
Task(Task &&other)
Definition task_pool.cc:70
Task & operator=(Task &&other)=delete
Task(const Task &other)=delete
TaskPool * pool
Definition task_pool.cc:39
TaskRunFunction run
Definition task_pool.cc:40
void operator()() const
Definition task_pool.cc:114
TaskFreeFunction freedata
Definition task_pool.cc:43
Task(TaskPool *pool, TaskRunFunction run, void *taskdata, bool free_taskdata, TaskFreeFunction freedata)
Definition task_pool.cc:45
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
TaskPool & operator=(TaskPool &&other)=delete
ThreadQueue * background_queue
Definition task_pool.cc:172
TaskPool(TaskPool &&other)=delete
void task_push(TaskRunFunction run, void *taskdata, bool free_taskdata, TaskFreeFunction freedata)
Definition task_pool.cc:251
bool use_threads
Definition task_pool.cc:159
eTaskPriority priority
Definition task_pool.cc:175
volatile bool is_suspended
Definition task_pool.cc:167
tbb::task_group tbb_group
Definition task.h:54
void * userdata
Definition task_pool.cc:161
TaskPool(const TaskPool &other)=delete
void work_and_wait()
Definition task_pool.cc:272
TaskPool & operator=(const TaskPool &other)=delete
TaskPool(const TaskPoolType type, const eTaskPriority priority, void *userdata)
Definition task_pool.cc:177
TaskPoolType type
Definition task_pool.cc:158
ListBase background_threads
Definition task_pool.cc:171
bool current_canceled()
Definition task_pool.cc:305
void cancel()
Definition task_pool.cc:290
volatile bool background_is_canceling
Definition task_pool.cc:173
blender::Vector< Task > suspended_tasks
Definition task_pool.cc:168
std::function< void()> TaskRunFunction
Definition task.h:18
TaskPool * BLI_task_pool_create_suspended(void *userdata, eTaskPriority priority)
Definition task_pool.cc:503
void * BLI_task_pool_user_data(TaskPool *pool)
Definition task_pool.cc:550
bool BLI_task_pool_current_canceled(TaskPool *pool)
Definition task_pool.cc:545
TaskPool * BLI_task_pool_create_no_threads(void *userdata)
Definition task_pool.cc:511
void BLI_task_pool_work_and_wait(TaskPool *pool)
Definition task_pool.cc:535
void BLI_task_pool_cancel(TaskPool *pool)
Definition task_pool.cc:540
TaskPool * BLI_task_pool_create_background(void *userdata, eTaskPriority priority)
Definition task_pool.cc:489
TaskPool * BLI_task_pool_create_background_serial(void *userdata, eTaskPriority priority)
Definition task_pool.cc:516
TaskPool * BLI_task_pool_create(void *userdata, eTaskPriority priority)
Definition task_pool.cc:484
void BLI_task_pool_free(TaskPool *pool)
Definition task_pool.cc:521
TaskPoolType
Definition task_pool.cc:149
@ TASK_POOL_TBB
Definition task_pool.cc:150
@ TASK_POOL_TBB_SUSPENDED
Definition task_pool.cc:151
@ TASK_POOL_NO_THREADS
Definition task_pool.cc:152
@ TASK_POOL_BACKGROUND
Definition task_pool.cc:153
@ TASK_POOL_BACKGROUND_SERIAL
Definition task_pool.cc:154
void BLI_task_pool_push(TaskPool *pool, TaskRunFunction run, void *taskdata, bool free_taskdata, TaskFreeFunction freedata)
Definition task_pool.cc:526