Blender V4.3
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
11#include <cstdlib>
12#include <memory>
13#include <utility>
14
15#include "MEM_guardedalloc.h"
16
17#include "DNA_listBase.h"
18
19#include "BLI_mempool.h"
20#include "BLI_task.h"
21#include "BLI_threads.h"
22
23#ifdef WITH_TBB
24# include <tbb/blocked_range.h>
25# include <tbb/task_arena.h>
26# include <tbb/task_group.h>
27#endif
28
34class Task {
35 public:
38 void *taskdata;
41
50
52 {
53 if (free_taskdata) {
54 if (freedata) {
56 }
57 else {
59 }
60 }
61 }
62
63 /* Move constructor.
64 * For performance, ensure we never copy the task and only move it.
65 * For TBB version 2017 and earlier we apply a workaround to make up for
66 * the lack of move constructor support. */
67 Task(Task &&other)
68 : pool(other.pool),
69 run(other.run),
70 taskdata(other.taskdata),
72 freedata(other.freedata)
73 {
74 other.pool = nullptr;
75 other.run = nullptr;
76 other.taskdata = nullptr;
77 other.free_taskdata = false;
78 other.freedata = nullptr;
79 }
80
81/* TBB has a check in `tbb/include/task_group.h` where `__TBB_CPP11_RVALUE_REF_PRESENT` should
82 * evaluate to true as with the other MSVC build. However, because of the clang compiler
83 * it does not and we attempt to call a deleted constructor in the tbb_task_pool_run function.
84 * This check fixes this issue and keeps our Task constructor valid. */
85#if (defined(WITH_TBB) && TBB_INTERFACE_VERSION_MAJOR < 10) || \
86 (defined(_MSC_VER) && defined(__clang__) && TBB_INTERFACE_VERSION_MAJOR < 12)
87 Task(const Task &other)
88 : pool(other.pool),
89 run(other.run),
90 taskdata(other.taskdata),
92 freedata(other.freedata)
93 {
94 ((Task &)other).pool = nullptr;
95 ((Task &)other).run = nullptr;
96 ((Task &)other).taskdata = nullptr;
97 ((Task &)other).free_taskdata = false;
98 ((Task &)other).freedata = nullptr;
99 }
100#else
101 Task(const Task &other) = delete;
102#endif
103
104 Task &operator=(const Task &other) = delete;
105 Task &operator=(Task &&other) = delete;
106
107 void operator()() const;
108};
109
110/* TBB Task Group.
111 *
112 * Subclass since there seems to be no other way to set priority. */
113
114#ifdef WITH_TBB
115class TBBTaskGroup : public tbb::task_group {
116 public:
117 TBBTaskGroup(eTaskPriority priority)
118 {
119# if TBB_INTERFACE_VERSION_MAJOR >= 12
120 /* TODO: support priorities in TBB 2021, where they are only available as
121 * part of task arenas, no longer for task groups. Or remove support for
122 * task priorities if they are no longer useful. */
123 UNUSED_VARS(priority);
124# else
125 switch (priority) {
127 my_context.set_priority(tbb::priority_low);
128 break;
130 my_context.set_priority(tbb::priority_normal);
131 break;
132 }
133# endif
134 }
135};
136#endif
137
138/* Task Pool */
139
147
148struct TaskPool {
151
153 void *userdata;
154
155#ifdef WITH_TBB
156 /* TBB task pool. */
157 TBBTaskGroup tbb_group;
158#endif
159 volatile bool is_suspended;
161
162 /* Background task pool. */
166};
167
168/* Execute task. */
170{
171 run(pool, taskdata);
172}
173
174/* TBB Task Pool.
175 *
176 * Task pool using the TBB scheduler for tasks. When building without TBB
177 * support or running Blender with -t 1, this reverts to single threaded.
178 *
179 * Tasks may be suspended until in all are created, to make it possible to
180 * initialize data structures and create tasks in a single pass. */
181
182static void tbb_task_pool_create(TaskPool *pool, eTaskPriority priority)
183{
184 if (pool->type == TASK_POOL_TBB_SUSPENDED) {
185 pool->is_suspended = true;
187 }
188
189#ifdef WITH_TBB
190 if (pool->use_threads) {
191 new (&pool->tbb_group) TBBTaskGroup(priority);
192 }
193#else
194 UNUSED_VARS(priority);
195#endif
196}
197
198static void tbb_task_pool_run(TaskPool *pool, Task &&task)
199{
200 if (pool->is_suspended) {
201 /* Suspended task that will be executed in work_and_wait(). */
202 Task *task_mem = (Task *)BLI_mempool_alloc(pool->suspended_mempool);
203 new (task_mem) Task(std::move(task));
204#ifdef __GNUC__
205 /* Work around apparent compiler bug where task is not properly copied
206 * to task_mem. This appears unrelated to the use of placement new or
207 * move semantics, happens even writing to a plain C struct. Rather the
208 * call into TBB seems to have some indirect effect. */
209 std::atomic_thread_fence(std::memory_order_release);
210#endif
211 }
212#ifdef WITH_TBB
213 else if (pool->use_threads) {
214 /* Execute in TBB task group. */
215 pool->tbb_group.run(std::move(task));
216 }
217#endif
218 else {
219 /* Execute immediately. */
220 task();
221 }
222}
223
225{
226 /* Start any suspended task now. */
227 if (pool->suspended_mempool) {
228 pool->is_suspended = false;
229
230 BLI_mempool_iter iter;
232 while (Task *task = (Task *)BLI_mempool_iterstep(&iter)) {
233 tbb_task_pool_run(pool, std::move(*task));
234 }
235
237 }
238
239#ifdef WITH_TBB
240 if (pool->use_threads) {
241 /* This is called wait(), but internally it can actually do work. This
242 * matters because we don't want recursive usage of task pools to run
243 * out of threads and get stuck. */
244 pool->tbb_group.wait();
245 }
246#endif
247}
248
250{
251#ifdef WITH_TBB
252 if (pool->use_threads) {
253 pool->tbb_group.cancel();
254 pool->tbb_group.wait();
255 }
256#else
257 UNUSED_VARS(pool);
258#endif
259}
260
262{
263#ifdef WITH_TBB
264 if (pool->use_threads) {
265 return tbb::is_current_task_group_canceling();
266 }
267#else
268 UNUSED_VARS(pool);
269#endif
270
271 return false;
272}
273
274static void tbb_task_pool_free(TaskPool *pool)
275{
276#ifdef WITH_TBB
277 if (pool->use_threads) {
278 pool->tbb_group.~TBBTaskGroup();
279 }
280#endif
281
282 if (pool->suspended_mempool) {
284 }
285}
286
287/* Background Task Pool.
288 *
289 * Fallback for running background tasks when building without TBB. */
290
291static void *background_task_run(void *userdata)
292{
293 TaskPool *pool = (TaskPool *)userdata;
294 while (Task *task = (Task *)BLI_thread_queue_pop(pool->background_queue)) {
295 (*task)();
296 task->~Task();
297 MEM_freeN(task);
298 }
299 return nullptr;
300}
301
307
308static void background_task_pool_run(TaskPool *pool, Task &&task)
309{
310 Task *task_mem = (Task *)MEM_mallocN(sizeof(Task), __func__);
311 new (task_mem) Task(std::move(task));
313
316 }
317}
318
320{
321 /* Signal background thread to stop waiting for new tasks if none are
322 * left, and wait for tasks and thread to finish. */
326}
327
329{
330 pool->background_is_canceling = true;
331
332 /* Remove tasks not yet started by background thread. */
334 while (Task *task = (Task *)BLI_thread_queue_pop(pool->background_queue)) {
335 task->~Task();
336 MEM_freeN(task);
337 }
338
339 /* Let background thread finish or cancel task it is working on. */
341 pool->background_is_canceling = false;
342}
343
345{
346 return pool->background_is_canceling;
347}
348
356
357/* Task Pool */
358
359static TaskPool *task_pool_create_ex(void *userdata, TaskPoolType type, eTaskPriority priority)
360{
361 const bool use_threads = BLI_task_scheduler_num_threads() > 1 && type != TASK_POOL_NO_THREADS;
362
363 /* Background task pool uses regular TBB scheduling if available. Only when
364 * building without TBB or running with -t 1 do we need to ensure these tasks
365 * do not block the main thread. */
366 if (type == TASK_POOL_BACKGROUND && use_threads) {
367 type = TASK_POOL_TBB;
368 }
369
370 /* Allocate task pool. */
371 TaskPool *pool = (TaskPool *)MEM_callocN(sizeof(TaskPool), "TaskPool");
372
373 pool->type = type;
374 pool->use_threads = use_threads;
375
376 pool->userdata = userdata;
378
379 switch (type) {
380 case TASK_POOL_TBB:
383 tbb_task_pool_create(pool, priority);
384 break;
388 break;
389 }
390
391 return pool;
392}
393
395{
396 return task_pool_create_ex(userdata, TASK_POOL_TBB, priority);
397}
398
400{
401 /* NOTE: In multi-threaded context, there is no differences with #BLI_task_pool_create(),
402 * but in single-threaded case it is ensured to have at least one worker thread to run on
403 * (i.e. you don't have to call #BLI_task_pool_work_and_wait
404 * on it to be sure it will be processed).
405 *
406 * NOTE: Background pools are non-recursive
407 * (that is, you should not create other background pools in tasks assigned to a background pool,
408 * they could end never being executed, since the 'fallback' background thread is already
409 * busy with parent task in single-threaded context). */
410 return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND, priority);
411}
412
414{
415 /* NOTE: Similar to #BLI_task_pool_create() but does not schedule any tasks for execution
416 * for until BLI_task_pool_work_and_wait() is called. This helps reducing threading
417 * overhead when pushing huge amount of small initial tasks from the main thread. */
418 return task_pool_create_ex(userdata, TASK_POOL_TBB_SUSPENDED, priority);
419}
420
425
427{
428 return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND_SERIAL, priority);
429}
430
432{
433 switch (pool->type) {
434 case TASK_POOL_TBB:
437 tbb_task_pool_free(pool);
438 break;
442 break;
443 }
444
446
447 MEM_freeN(pool);
448}
449
451 TaskRunFunction run,
452 void *taskdata,
453 bool free_taskdata,
454 TaskFreeFunction freedata)
455{
456 Task task(pool, run, taskdata, free_taskdata, freedata);
457
458 switch (pool->type) {
459 case TASK_POOL_TBB:
462 tbb_task_pool_run(pool, std::move(task));
463 break;
466 background_task_pool_run(pool, std::move(task));
467 break;
468 }
469}
470
472{
473 switch (pool->type) {
474 case TASK_POOL_TBB:
478 break;
482 break;
483 }
484}
485
487{
488 switch (pool->type) {
489 case TASK_POOL_TBB:
493 break;
497 break;
498 }
499}
500
502{
503 switch (pool->type) {
504 case TASK_POOL_TBB:
507 return tbb_task_pool_canceled(pool);
511 }
512 BLI_assert_msg(0, "BLI_task_pool_canceled: Control flow should not come here!");
513 return false;
514}
515
517{
518 return pool->userdata;
519}
520
522{
523 return &pool->user_mutex;
524}
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
void * BLI_mempool_alloc(BLI_mempool *pool) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL ATTR_NONNULL(1)
void BLI_mempool_iternew(BLI_mempool *pool, BLI_mempool_iter *iter) ATTR_NONNULL()
void * BLI_mempool_iterstep(BLI_mempool_iter *iter) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
BLI_mempool * BLI_mempool_create(unsigned int esize, unsigned int elem_num, unsigned int pchunk, unsigned int flag) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL
@ BLI_MEMPOOL_ALLOW_ITER
Definition BLI_mempool.h:95
void BLI_mempool_destroy(BLI_mempool *pool) ATTR_NONNULL(1)
void BLI_mempool_clear(BLI_mempool *pool) ATTR_NONNULL(1)
int BLI_task_scheduler_num_threads(void)
eTaskPriority
Definition BLI_task.h:55
@ TASK_PRIORITY_LOW
Definition BLI_task.h:56
@ TASK_PRIORITY_HIGH
Definition BLI_task.h:57
void(* TaskFreeFunction)(TaskPool *__restrict pool, void *taskdata)
Definition BLI_task.h:62
void BLI_thread_queue_push(ThreadQueue *queue, void *work)
Definition threads.cc:644
void * BLI_thread_queue_pop(ThreadQueue *queue)
Definition threads.cc:655
void BLI_mutex_end(ThreadMutex *mutex)
Definition threads.cc:360
ThreadQueue * BLI_thread_queue_init(void)
Definition threads.cc:618
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_mutex_init(ThreadMutex *mutex)
Definition threads.cc:340
void BLI_thread_queue_free(ThreadQueue *queue)
Definition threads.cc:632
void BLI_threadpool_end(struct ListBase *threadbase)
Definition threads.cc:234
void BLI_thread_queue_nowait(ThreadQueue *queue)
Definition threads.cc:770
void BLI_thread_queue_wait_finish(ThreadQueue *queue)
Definition threads.cc:781
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
pthread_mutex_t ThreadMutex
Definition BLI_threads.h:83
#define UNUSED_VARS(...)
These structs are the foundation for all linked lists in the library system.
Read Guarded memory(de)allocation.
void * taskdata
Definition task_pool.cc:38
Task & operator=(const Task &other)=delete
~Task()
Definition task_pool.cc:51
bool free_taskdata
Definition task_pool.cc:39
Task(Task &&other)
Definition task_pool.cc:67
Task & operator=(Task &&other)=delete
Task(const Task &other)=delete
TaskPool * pool
Definition task_pool.cc:36
TaskRunFunction run
Definition task_pool.cc:37
void operator()() const
Definition task_pool.cc:169
TaskFreeFunction freedata
Definition task_pool.cc:40
Task(TaskPool *pool, TaskRunFunction run, void *taskdata, bool free_taskdata, TaskFreeFunction freedata)
Definition task_pool.cc:42
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
ThreadQueue * background_queue
Definition task_pool.cc:164
bool use_threads
Definition task_pool.cc:150
volatile bool is_suspended
Definition task_pool.cc:159
tbb::task_group tbb_group
Definition task.h:55
void * userdata
Definition task_pool.cc:153
ThreadMutex user_mutex
Definition task_pool.cc:152
TaskPoolType type
Definition task_pool.cc:149
ListBase background_threads
Definition task_pool.cc:163
volatile bool background_is_canceling
Definition task_pool.cc:165
BLI_mempool * suspended_mempool
Definition task_pool.cc:160
function< void(void)> TaskRunFunction
Definition task.h:19
static void tbb_task_pool_run(TaskPool *pool, Task &&task)
Definition task_pool.cc:198
static bool background_task_pool_canceled(TaskPool *pool)
Definition task_pool.cc:344
static bool tbb_task_pool_canceled(TaskPool *pool)
Definition task_pool.cc:261
TaskPool * BLI_task_pool_create_suspended(void *userdata, eTaskPriority priority)
Definition task_pool.cc:413
static void tbb_task_pool_cancel(TaskPool *pool)
Definition task_pool.cc:249
void * BLI_task_pool_user_data(TaskPool *pool)
Definition task_pool.cc:516
static void background_task_pool_run(TaskPool *pool, Task &&task)
Definition task_pool.cc:308
static void background_task_pool_work_and_wait(TaskPool *pool)
Definition task_pool.cc:319
bool BLI_task_pool_current_canceled(TaskPool *pool)
Definition task_pool.cc:501
TaskPool * BLI_task_pool_create_no_threads(void *userdata)
Definition task_pool.cc:421
void BLI_task_pool_work_and_wait(TaskPool *pool)
Definition task_pool.cc:471
void BLI_task_pool_cancel(TaskPool *pool)
Definition task_pool.cc:486
static void background_task_pool_free(TaskPool *pool)
Definition task_pool.cc:349
static void tbb_task_pool_work_and_wait(TaskPool *pool)
Definition task_pool.cc:224
ThreadMutex * BLI_task_pool_user_mutex(TaskPool *pool)
Definition task_pool.cc:521
static void tbb_task_pool_create(TaskPool *pool, eTaskPriority priority)
Definition task_pool.cc:182
TaskPool * BLI_task_pool_create_background(void *userdata, eTaskPriority priority)
Definition task_pool.cc:399
TaskPool * BLI_task_pool_create_background_serial(void *userdata, eTaskPriority priority)
Definition task_pool.cc:426
TaskPool * BLI_task_pool_create(void *userdata, eTaskPriority priority)
Definition task_pool.cc:394
static void * background_task_run(void *userdata)
Definition task_pool.cc:291
static void background_task_pool_create(TaskPool *pool)
Definition task_pool.cc:302
static void tbb_task_pool_free(TaskPool *pool)
Definition task_pool.cc:274
static TaskPool * task_pool_create_ex(void *userdata, TaskPoolType type, eTaskPriority priority)
Definition task_pool.cc:359
static void background_task_pool_cancel(TaskPool *pool)
Definition task_pool.cc:328
void BLI_task_pool_free(TaskPool *pool)
Definition task_pool.cc:431
TaskPoolType
Definition task_pool.cc:140
@ TASK_POOL_TBB
Definition task_pool.cc:141
@ TASK_POOL_TBB_SUSPENDED
Definition task_pool.cc:142
@ TASK_POOL_NO_THREADS
Definition task_pool.cc:143
@ TASK_POOL_BACKGROUND
Definition task_pool.cc:144
@ TASK_POOL_BACKGROUND_SERIAL
Definition task_pool.cc:145
void BLI_task_pool_push(TaskPool *pool, TaskRunFunction run, void *taskdata, bool free_taskdata, TaskFreeFunction freedata)
Definition task_pool.cc:450