Blender  V3.3
task_pool.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
9 #include <cstdlib>
10 #include <memory>
11 #include <utility>
12 
13 #include "MEM_guardedalloc.h"
14 
15 #include "DNA_listBase.h"
16 
17 #include "BLI_math.h"
18 #include "BLI_mempool.h"
19 #include "BLI_task.h"
20 #include "BLI_threads.h"
21 
22 #ifdef WITH_TBB
23 # include <tbb/blocked_range.h>
24 # include <tbb/task_arena.h>
25 # include <tbb/task_group.h>
26 #endif
27 
28 /* Task
29  *
30  * Unit of work to execute. This is a C++ class to work with TBB. */
31 
32 class Task {
33  public:
36  void *taskdata;
39 
42  void *taskdata,
43  bool free_taskdata,
46  {
47  }
48 
50  {
51  if (free_taskdata) {
52  if (freedata) {
54  }
55  else {
57  }
58  }
59  }
60 
61  /* Move constructor.
62  * For performance, ensure we never copy the task and only move it.
63  * For TBB version 2017 and earlier we apply a workaround to make up for
64  * the lack of move constructor support. */
65  Task(Task &&other)
66  : pool(other.pool),
67  run(other.run),
68  taskdata(other.taskdata),
70  freedata(other.freedata)
71  {
72  other.pool = nullptr;
73  other.run = nullptr;
74  other.taskdata = nullptr;
75  other.free_taskdata = false;
76  other.freedata = nullptr;
77  }
78 
79 #if defined(WITH_TBB) && TBB_INTERFACE_VERSION_MAJOR < 10
80  Task(const Task &other)
81  : pool(other.pool),
82  run(other.run),
83  taskdata(other.taskdata),
85  freedata(other.freedata)
86  {
87  ((Task &)other).pool = NULL;
88  ((Task &)other).run = NULL;
89  ((Task &)other).taskdata = NULL;
90  ((Task &)other).free_taskdata = false;
91  ((Task &)other).freedata = NULL;
92  }
93 #else
94  Task(const Task &other) = delete;
95 #endif
96 
97  Task &operator=(const Task &other) = delete;
98  Task &operator=(Task &&other) = delete;
99 
100  void operator()() const;
101 };
102 
103 /* TBB Task Group.
104  *
105  * Subclass since there seems to be no other way to set priority. */
106 
107 #ifdef WITH_TBB
108 class TBBTaskGroup : public tbb::task_group {
109  public:
110  TBBTaskGroup(eTaskPriority priority)
111  {
112 # if TBB_INTERFACE_VERSION_MAJOR >= 12
113  /* TODO: support priorities in TBB 2021, where they are only available as
114  * part of task arenas, no longer for task groups. Or remove support for
115  * task priorities if they are no longer useful. */
116  UNUSED_VARS(priority);
117 # else
118  switch (priority) {
119  case TASK_PRIORITY_LOW:
120  my_context.set_priority(tbb::priority_low);
121  break;
122  case TASK_PRIORITY_HIGH:
123  my_context.set_priority(tbb::priority_normal);
124  break;
125  }
126 # endif
127  }
128 };
129 #endif
130 
131 /* Task Pool */
132 
139 };
140 
141 struct TaskPool {
144 
146  void *userdata;
147 
148 #ifdef WITH_TBB
149  /* TBB task pool. */
150  TBBTaskGroup tbb_group;
151 #endif
152  volatile bool is_suspended;
154 
155  /* Background task pool. */
158  volatile bool background_is_canceling;
159 };
160 
161 /* Execute task. */
162 void Task::operator()() const
163 {
164  run(pool, taskdata);
165 }
166 
167 /* TBB Task Pool.
168  *
169  * Task pool using the TBB scheduler for tasks. When building without TBB
170  * support or running Blender with -t 1, this reverts to single threaded.
171  *
172  * Tasks may be suspended until in all are created, to make it possible to
173  * initialize data structures and create tasks in a single pass. */
174 
176 {
178  pool->is_suspended = true;
180  }
181 
182 #ifdef WITH_TBB
183  if (pool->use_threads) {
184  new (&pool->tbb_group) TBBTaskGroup(priority);
185  }
186 #else
187  UNUSED_VARS(priority);
188 #endif
189 }
190 
192 {
193  if (pool->is_suspended) {
194  /* Suspended task that will be executed in work_and_wait(). */
196  new (task_mem) Task(std::move(task));
197 #ifdef __GNUC__
198  /* Work around apparent compiler bug where task is not properly copied
199  * to task_mem. This appears unrelated to the use of placement new or
200  * move semantics, happens even writing to a plain C struct. Rather the
201  * call into TBB seems to have some indirect effect. */
202  std::atomic_thread_fence(std::memory_order_release);
203 #endif
204  }
205 #ifdef WITH_TBB
206  else if (pool->use_threads) {
207  /* Execute in TBB task group. */
208  pool->tbb_group.run(std::move(task));
209  }
210 #endif
211  else {
212  /* Execute immediately. */
213  task();
214  }
215 }
216 
218 {
219  /* Start any suspended task now. */
220  if (pool->suspended_mempool) {
221  pool->is_suspended = false;
222 
223  BLI_mempool_iter iter;
225  while (Task *task = (Task *)BLI_mempool_iterstep(&iter)) {
226  tbb_task_pool_run(pool, std::move(*task));
227  }
228 
230  }
231 
232 #ifdef WITH_TBB
233  if (pool->use_threads) {
234  /* This is called wait(), but internally it can actually do work. This
235  * matters because we don't want recursive usage of task pools to run
236  * out of threads and get stuck. */
237  pool->tbb_group.wait();
238  }
239 #endif
240 }
241 
243 {
244 #ifdef WITH_TBB
245  if (pool->use_threads) {
246  pool->tbb_group.cancel();
247  pool->tbb_group.wait();
248  }
249 #else
250  UNUSED_VARS(pool);
251 #endif
252 }
253 
255 {
256 #ifdef WITH_TBB
257  if (pool->use_threads) {
258  return tbb::is_current_task_group_canceling();
259  }
260 #else
261  UNUSED_VARS(pool);
262 #endif
263 
264  return false;
265 }
266 
268 {
269 #ifdef WITH_TBB
270  if (pool->use_threads) {
271  pool->tbb_group.~TBBTaskGroup();
272  }
273 #endif
274 
275  if (pool->suspended_mempool) {
277  }
278 }
279 
280 /* Background Task Pool.
281  *
282  * Fallback for running background tasks when building without TBB. */
283 
284 static void *background_task_run(void *userdata)
285 {
286  TaskPool *pool = (TaskPool *)userdata;
288  (*task)();
289  task->~Task();
290  MEM_freeN(task);
291  }
292  return nullptr;
293 }
294 
296 {
299 }
300 
302 {
303  Task *task_mem = (Task *)MEM_mallocN(sizeof(Task), __func__);
304  new (task_mem) Task(std::move(task));
306 
309  }
310 }
311 
313 {
314  /* Signal background thread to stop waiting for new tasks if none are
315  * left, and wait for tasks and thread to finish. */
319 }
320 
322 {
324 
325  /* Remove tasks not yet started by background thread. */
328  task->~Task();
329  MEM_freeN(task);
330  }
331 
332  /* Let background thread finish or cancel task it is working on. */
334  pool->background_is_canceling = false;
335 }
336 
338 {
340 }
341 
343 {
345 
348 }
349 
350 /* Task Pool */
351 
352 static TaskPool *task_pool_create_ex(void *userdata, TaskPoolType type, eTaskPriority priority)
353 {
354  const bool use_threads = BLI_task_scheduler_num_threads() > 1 && type != TASK_POOL_NO_THREADS;
355 
356  /* Background task pool uses regular TBB scheduling if available. Only when
357  * building without TBB or running with -t 1 do we need to ensure these tasks
358  * do not block the main thread. */
359  if (type == TASK_POOL_BACKGROUND && use_threads) {
361  }
362 
363  /* Allocate task pool. */
364  TaskPool *pool = (TaskPool *)MEM_callocN(sizeof(TaskPool), "TaskPool");
365 
366  pool->type = type;
367  pool->use_threads = use_threads;
368 
369  pool->userdata = userdata;
371 
372  switch (type) {
373  case TASK_POOL_TBB:
376  tbb_task_pool_create(pool, priority);
377  break;
381  break;
382  }
383 
384  return pool;
385 }
386 
390 TaskPool *BLI_task_pool_create(void *userdata, eTaskPriority priority)
391 {
392  return task_pool_create_ex(userdata, TASK_POOL_TBB, priority);
393 }
394 
408 {
409  return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND, priority);
410 }
411 
418 {
419  return task_pool_create_ex(userdata, TASK_POOL_TBB_SUSPENDED, priority);
420 }
421 
427 {
429 }
430 
436 {
437  return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND_SERIAL, priority);
438 }
439 
441 {
442  switch (pool->type) {
443  case TASK_POOL_TBB:
447  break;
451  break;
452  }
453 
455 
456  MEM_freeN(pool);
457 }
458 
460  TaskRunFunction run,
461  void *taskdata,
462  bool free_taskdata,
463  TaskFreeFunction freedata)
464 {
465  Task task(pool, run, taskdata, free_taskdata, freedata);
466 
467  switch (pool->type) {
468  case TASK_POOL_TBB:
471  tbb_task_pool_run(pool, std::move(task));
472  break;
475  background_task_pool_run(pool, std::move(task));
476  break;
477  }
478 }
479 
481 {
482  switch (pool->type) {
483  case TASK_POOL_TBB:
487  break;
491  break;
492  }
493 }
494 
496 {
497  switch (pool->type) {
498  case TASK_POOL_TBB:
502  break;
506  break;
507  }
508 }
509 
511 {
512  switch (pool->type) {
513  case TASK_POOL_TBB:
520  }
521  BLI_assert_msg(0, "BLI_task_pool_canceled: Control flow should not come here!");
522  return false;
523 }
524 
526 {
527  return pool->userdata;
528 }
529 
531 {
532  return &pool->user_mutex;
533 }
#define BLI_assert_msg(a, msg)
Definition: BLI_assert.h:53
void BLI_mempool_iternew(BLI_mempool *pool, BLI_mempool_iter *iter) ATTR_NONNULL()
Definition: BLI_mempool.c:498
@ BLI_MEMPOOL_ALLOW_ITER
Definition: BLI_mempool.h:107
void * BLI_mempool_iterstep(BLI_mempool_iter *iter) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: BLI_mempool.c:577
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
Definition: BLI_mempool.c:253
void * BLI_mempool_alloc(BLI_mempool *pool) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL ATTR_NONNULL(1)
Definition: BLI_mempool.c:319
void BLI_mempool_destroy(BLI_mempool *pool) ATTR_NONNULL(1)
Definition: BLI_mempool.c:707
void BLI_mempool_clear(BLI_mempool *pool) ATTR_NONNULL(1)
Definition: BLI_mempool.c:702
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:641
void BLI_mutex_end(ThreadMutex *mutex)
Definition: threads.cc:388
void BLI_threadpool_remove(struct ListBase *threadbase, void *callerdata)
Definition: threads.cc:225
void BLI_threadpool_init(struct ListBase *threadbase, void *(*do_thread)(void *), int tot)
Definition: threads.cc:134
void BLI_mutex_init(ThreadMutex *mutex)
Definition: threads.cc:368
void BLI_thread_queue_free(ThreadQueue *queue)
Definition: threads.cc:629
void BLI_threadpool_end(struct ListBase *threadbase)
Definition: threads.cc:262
void BLI_thread_queue_nowait(ThreadQueue *queue)
Definition: threads.cc:767
void BLI_thread_queue_wait_finish(ThreadQueue *queue)
Definition: threads.cc:778
void BLI_threadpool_clear(struct ListBase *threadbase)
Definition: threads.cc:251
int BLI_available_threads(struct ListBase *threadbase)
Definition: threads.cc:167
void * BLI_thread_queue_pop(ThreadQueue *queue)
Definition: threads.cc:652
void BLI_threadpool_insert(struct ListBase *threadbase, void *callerdata)
Definition: threads.cc:212
ThreadQueue * BLI_thread_queue_init(void)
Definition: threads.cc:615
pthread_mutex_t ThreadMutex
Definition: BLI_threads.h:82
#define UNUSED_VARS(...)
These structs are the foundation for all linked lists in the library system.
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum type
Read Guarded memory(de)allocation.
Task & operator=(const Task &other)=delete
void * taskdata
Definition: task_pool.cc:36
~Task()
Definition: task_pool.cc:49
bool free_taskdata
Definition: task_pool.cc:37
Task & operator=(Task &&other)=delete
Task(Task &&other)
Definition: task_pool.cc:65
Task(const Task &other)=delete
TaskPool * pool
Definition: task_pool.cc:34
TaskRunFunction run
Definition: task_pool.cc:35
void operator()() const
Definition: task_pool.cc:162
TaskFreeFunction freedata
Definition: task_pool.cc:38
Task(TaskPool *pool, TaskRunFunction run, void *taskdata, bool free_taskdata, TaskFreeFunction freedata)
Definition: task_pool.cc:40
void(* MEM_freeN)(void *vmemh)
Definition: mallocn.c:27
void *(* MEM_callocN)(size_t len, const char *str)
Definition: mallocn.c:31
void *(* MEM_mallocN)(size_t len, const char *str)
Definition: mallocn.c:33
struct blender::compositor::@179::@181 task
ThreadQueue * background_queue
Definition: task_pool.cc:157
bool use_threads
Definition: task_pool.cc:143
volatile bool is_suspended
Definition: task_pool.cc:152
tbb::task_group tbb_group
Definition: task.h:54
void * userdata
Definition: task_pool.cc:146
ThreadMutex user_mutex
Definition: task_pool.cc:145
TaskPoolType type
Definition: task_pool.cc:142
ListBase background_threads
Definition: task_pool.cc:156
volatile bool background_is_canceling
Definition: task_pool.cc:158
BLI_mempool * suspended_mempool
Definition: task_pool.cc:153
function< void(void)> TaskRunFunction
Definition: task.h:16
static void tbb_task_pool_run(TaskPool *pool, Task &&task)
Definition: task_pool.cc:191
static bool background_task_pool_canceled(TaskPool *pool)
Definition: task_pool.cc:337
static bool tbb_task_pool_canceled(TaskPool *pool)
Definition: task_pool.cc:254
static void tbb_task_pool_cancel(TaskPool *pool)
Definition: task_pool.cc:242
static void * background_task_run(void *userdata)
Definition: task_pool.cc:284
static void background_task_pool_run(TaskPool *pool, Task &&task)
Definition: task_pool.cc:301
static void background_task_pool_work_and_wait(TaskPool *pool)
Definition: task_pool.cc:312
void * BLI_task_pool_user_data(TaskPool *pool)
Definition: task_pool.cc:525
bool BLI_task_pool_current_canceled(TaskPool *pool)
Definition: task_pool.cc:510
void BLI_task_pool_work_and_wait(TaskPool *pool)
Definition: task_pool.cc:480
void BLI_task_pool_cancel(TaskPool *pool)
Definition: task_pool.cc:495
static TaskPool * task_pool_create_ex(void *userdata, TaskPoolType type, eTaskPriority priority)
Definition: task_pool.cc:352
static void background_task_pool_free(TaskPool *pool)
Definition: task_pool.cc:342
static void tbb_task_pool_work_and_wait(TaskPool *pool)
Definition: task_pool.cc:217
TaskPool * BLI_task_pool_create_background_serial(void *userdata, eTaskPriority priority)
Definition: task_pool.cc:435
static void tbb_task_pool_create(TaskPool *pool, eTaskPriority priority)
Definition: task_pool.cc:175
ThreadMutex * BLI_task_pool_user_mutex(TaskPool *pool)
Definition: task_pool.cc:530
TaskPool * BLI_task_pool_create(void *userdata, eTaskPriority priority)
Definition: task_pool.cc:390
TaskPool * BLI_task_pool_create_background(void *userdata, eTaskPriority priority)
Definition: task_pool.cc:407
TaskPool * BLI_task_pool_create_suspended(void *userdata, eTaskPriority priority)
Definition: task_pool.cc:417
static void background_task_pool_create(TaskPool *pool)
Definition: task_pool.cc:295
static void tbb_task_pool_free(TaskPool *pool)
Definition: task_pool.cc:267
TaskPool * BLI_task_pool_create_no_threads(void *userdata)
Definition: task_pool.cc:426
static void background_task_pool_cancel(TaskPool *pool)
Definition: task_pool.cc:321
void BLI_task_pool_free(TaskPool *pool)
Definition: task_pool.cc:440
TaskPoolType
Definition: task_pool.cc:133
@ TASK_POOL_TBB
Definition: task_pool.cc:134
@ TASK_POOL_TBB_SUSPENDED
Definition: task_pool.cc:135
@ TASK_POOL_NO_THREADS
Definition: task_pool.cc:136
@ TASK_POOL_BACKGROUND
Definition: task_pool.cc:137
@ TASK_POOL_BACKGROUND_SERIAL
Definition: task_pool.cc:138
void BLI_task_pool_push(TaskPool *pool, TaskRunFunction run, void *taskdata, bool free_taskdata, TaskFreeFunction freedata)
Definition: task_pool.cc:459