Blender  V2.93
task_pool.cc
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16 
23 #include <cstdlib>
24 #include <memory>
25 #include <utility>
26 
27 #include "MEM_guardedalloc.h"
28 
29 #include "DNA_listBase.h"
30 
31 #include "BLI_math.h"
32 #include "BLI_mempool.h"
33 #include "BLI_task.h"
34 #include "BLI_threads.h"
35 
36 #ifdef WITH_TBB
37 # include <tbb/blocked_range.h>
38 # include <tbb/task_arena.h>
39 # include <tbb/task_group.h>
40 #endif
41 
42 /* Task
43  *
44  * Unit of work to execute. This is a C++ class to work with TBB. */
45 
46 class Task {
47  public:
50  void *taskdata;
53 
56  void *taskdata,
57  bool free_taskdata,
60  {
61  }
62 
64  {
65  if (free_taskdata) {
66  if (freedata) {
68  }
69  else {
71  }
72  }
73  }
74 
75  /* Move constructor.
76  * For performance, ensure we never copy the task and only move it.
77  * For TBB version 2017 and earlier we apply a workaround to make up for
78  * the lack of move constructor support. */
79  Task(Task &&other)
80  : pool(other.pool),
81  run(other.run),
82  taskdata(other.taskdata),
84  freedata(other.freedata)
85  {
86  other.pool = nullptr;
87  other.run = nullptr;
88  other.taskdata = nullptr;
89  other.free_taskdata = false;
90  other.freedata = nullptr;
91  }
92 
93 #if defined(WITH_TBB) && TBB_INTERFACE_VERSION_MAJOR < 10
94  Task(const Task &other)
95  : pool(other.pool),
96  run(other.run),
97  taskdata(other.taskdata),
99  freedata(other.freedata)
100  {
101  ((Task &)other).pool = NULL;
102  ((Task &)other).run = NULL;
103  ((Task &)other).taskdata = NULL;
104  ((Task &)other).free_taskdata = false;
105  ((Task &)other).freedata = NULL;
106  }
107 #else
108  Task(const Task &other) = delete;
109 #endif
110 
111  Task &operator=(const Task &other) = delete;
112  Task &operator=(Task &&other) = delete;
113 
114  /* Execute task. */
115  void operator()() const
116  {
117 #ifdef WITH_TBB
118  tbb::this_task_arena::isolate([this] { run(pool, taskdata); });
119 #else
120  run(pool, taskdata);
121 #endif
122  }
123 };
124 
125 /* TBB Task Group.
126  *
127  * Subclass since there seems to be no other way to set priority. */
128 
129 #ifdef WITH_TBB
130 class TBBTaskGroup : public tbb::task_group {
131  public:
132  TBBTaskGroup(TaskPriority priority)
133  {
134 # if TBB_INTERFACE_VERSION_MAJOR >= 12
135  /* TODO: support priorities in TBB 2021, where they are only available as
136  * part of task arenas, no longer for task groups. Or remove support for
137  * task priorities if they are no longer useful. */
138  UNUSED_VARS(priority);
139 # else
140  switch (priority) {
141  case TASK_PRIORITY_LOW:
142  my_context.set_priority(tbb::priority_low);
143  break;
144  case TASK_PRIORITY_HIGH:
145  my_context.set_priority(tbb::priority_normal);
146  break;
147  }
148 # endif
149  }
150 };
151 #endif
152 
153 /* Task Pool */
154 
161 };
162 
163 struct TaskPool {
166 
168  void *userdata;
169 
170  /* TBB task pool. */
171 #ifdef WITH_TBB
172  TBBTaskGroup tbb_group;
173 #endif
174  volatile bool is_suspended;
176 
177  /* Background task pool. */
180  volatile bool background_is_canceling;
181 };
182 
183 /* TBB Task Pool.
184  *
185  * Task pool using the TBB scheduler for tasks. When building without TBB
186  * support or running Blender with -t 1, this reverts to single threaded.
187  *
188  * Tasks may be suspended until in all are created, to make it possible to
189  * initialize data structures and create tasks in a single pass. */
190 
192 {
194  pool->is_suspended = true;
196  }
197 
198 #ifdef WITH_TBB
199  if (pool->use_threads) {
200  new (&pool->tbb_group) TBBTaskGroup(priority);
201  }
202 #else
203  UNUSED_VARS(priority);
204 #endif
205 }
206 
208 {
209  if (pool->is_suspended) {
210  /* Suspended task that will be executed in work_and_wait(). */
212  new (task_mem) Task(std::move(task));
213 #ifdef __GNUC__
214  /* Work around apparent compiler bug where task is not properly copied
215  * to task_mem. This appears unrelated to the use of placement new or
216  * move semantics, happens even writing to a plain C struct. Rather the
217  * call into TBB seems to have some indirect effect. */
218  std::atomic_thread_fence(std::memory_order_release);
219 #endif
220  }
221 #ifdef WITH_TBB
222  else if (pool->use_threads) {
223  /* Execute in TBB task group. */
224  pool->tbb_group.run(std::move(task));
225  }
226 #endif
227  else {
228  /* Execute immediately. */
229  task();
230  }
231 }
232 
234 {
235  /* Start any suspended task now. */
236  if (pool->suspended_mempool) {
237  pool->is_suspended = false;
238 
239  BLI_mempool_iter iter;
241  while (Task *task = (Task *)BLI_mempool_iterstep(&iter)) {
242  tbb_task_pool_run(pool, std::move(*task));
243  }
244 
246  }
247 
248 #ifdef WITH_TBB
249  if (pool->use_threads) {
250  /* This is called wait(), but internally it can actually do work. This
251  * matters because we don't want recursive usage of task pools to run
252  * out of threads and get stuck. */
253  pool->tbb_group.wait();
254  }
255 #endif
256 }
257 
259 {
260 #ifdef WITH_TBB
261  if (pool->use_threads) {
262  pool->tbb_group.cancel();
263  pool->tbb_group.wait();
264  }
265 #else
266  UNUSED_VARS(pool);
267 #endif
268 }
269 
271 {
272 #ifdef WITH_TBB
273  if (pool->use_threads) {
274  return tbb::is_current_task_group_canceling();
275  }
276 #else
277  UNUSED_VARS(pool);
278 #endif
279 
280  return false;
281 }
282 
284 {
285 #ifdef WITH_TBB
286  if (pool->use_threads) {
287  pool->tbb_group.~TBBTaskGroup();
288  }
289 #endif
290 
291  if (pool->suspended_mempool) {
293  }
294 }
295 
296 /* Background Task Pool.
297  *
298  * Fallback for running background tasks when building without TBB. */
299 
300 static void *background_task_run(void *userdata)
301 {
302  TaskPool *pool = (TaskPool *)userdata;
304  (*task)();
305  task->~Task();
306  MEM_freeN(task);
307  }
308  return nullptr;
309 }
310 
312 {
315 }
316 
318 {
319  Task *task_mem = (Task *)MEM_mallocN(sizeof(Task), __func__);
320  new (task_mem) Task(std::move(task));
322 
325  }
326 }
327 
329 {
330  /* Signal background thread to stop waiting for new tasks if none are
331  * left, and wait for tasks and thread to finish. */
335 }
336 
338 {
340 
341  /* Remove tasks not yet started by background thread. */
344  task->~Task();
345  MEM_freeN(task);
346  }
347 
348  /* Let background thread finish or cancel task it is working on. */
350  pool->background_is_canceling = false;
351 }
352 
354 {
356 }
357 
359 {
361 
364 }
365 
366 /* Task Pool */
367 
368 static TaskPool *task_pool_create_ex(void *userdata, TaskPoolType type, TaskPriority priority)
369 {
370  const bool use_threads = BLI_task_scheduler_num_threads() > 1 && type != TASK_POOL_NO_THREADS;
371 
372  /* Background task pool uses regular TBB scheduling if available. Only when
373  * building without TBB or running with -t 1 do we need to ensure these tasks
374  * do not block the main thread. */
375  if (type == TASK_POOL_BACKGROUND && use_threads) {
377  }
378 
379  /* Allocate task pool. */
380  TaskPool *pool = (TaskPool *)MEM_callocN(sizeof(TaskPool), "TaskPool");
381 
382  pool->type = type;
383  pool->use_threads = use_threads;
384 
385  pool->userdata = userdata;
387 
388  switch (type) {
389  case TASK_POOL_TBB:
392  tbb_task_pool_create(pool, priority);
393  break;
397  break;
398  }
399 
400  return pool;
401 }
402 
406 TaskPool *BLI_task_pool_create(void *userdata, TaskPriority priority)
407 {
408  return task_pool_create_ex(userdata, TASK_POOL_TBB, priority);
409 }
410 
424 {
425  return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND, priority);
426 }
427 
434 {
435  return task_pool_create_ex(userdata, TASK_POOL_TBB_SUSPENDED, priority);
436 }
437 
443 {
445 }
446 
452 {
453  return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND_SERIAL, priority);
454 }
455 
457 {
458  switch (pool->type) {
459  case TASK_POOL_TBB:
463  break;
467  break;
468  }
469 
471 
472  MEM_freeN(pool);
473 }
474 
476  TaskRunFunction run,
477  void *taskdata,
478  bool free_taskdata,
479  TaskFreeFunction freedata)
480 {
481  Task task(pool, run, taskdata, free_taskdata, freedata);
482 
483  switch (pool->type) {
484  case TASK_POOL_TBB:
487  tbb_task_pool_run(pool, std::move(task));
488  break;
491  background_task_pool_run(pool, std::move(task));
492  break;
493  }
494 }
495 
497 {
498  switch (pool->type) {
499  case TASK_POOL_TBB:
503  break;
507  break;
508  }
509 }
510 
512 {
513  switch (pool->type) {
514  case TASK_POOL_TBB:
518  break;
522  break;
523  }
524 }
525 
527 {
528  switch (pool->type) {
529  case TASK_POOL_TBB:
536  }
537  BLI_assert("BLI_task_pool_canceled: Control flow should not come here!");
538  return false;
539 }
540 
542 {
543  return pool->userdata;
544 }
545 
547 {
548  return &pool->user_mutex;
549 }
#define BLI_assert(a)
Definition: BLI_assert.h:58
void BLI_mempool_iternew(BLI_mempool *pool, BLI_mempool_iter *iter) ATTR_NONNULL()
Definition: BLI_mempool.c:537
@ BLI_MEMPOOL_ALLOW_ITER
Definition: BLI_mempool.h:85
void * BLI_mempool_iterstep(BLI_mempool_iter *iter) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: BLI_mempool.c:645
BLI_mempool * BLI_mempool_create(unsigned int esize, unsigned int totelem, unsigned int pchunk, unsigned int flag) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
Definition: BLI_mempool.c:268
void * BLI_mempool_alloc(BLI_mempool *pool) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition: BLI_mempool.c:334
void BLI_mempool_destroy(BLI_mempool *pool) ATTR_NONNULL(1)
Definition: BLI_mempool.c:757
void BLI_mempool_clear(BLI_mempool *pool) ATTR_NONNULL(1)
Definition: BLI_mempool.c:749
int BLI_task_scheduler_num_threads(void)
void(* TaskFreeFunction)(TaskPool *__restrict pool, void *taskdata)
Definition: BLI_task.h:72
TaskPriority
Definition: BLI_task.h:65
@ TASK_PRIORITY_LOW
Definition: BLI_task.h:66
@ TASK_PRIORITY_HIGH
Definition: BLI_task.h:67
void BLI_thread_queue_push(ThreadQueue *queue, void *work)
Definition: threads.cc:669
void BLI_mutex_end(ThreadMutex *mutex)
Definition: threads.cc:416
void BLI_threadpool_remove(struct ListBase *threadbase, void *callerdata)
Definition: threads.cc:252
void BLI_threadpool_init(struct ListBase *threadbase, void *(*do_thread)(void *), int tot)
Definition: threads.cc:159
void BLI_mutex_init(ThreadMutex *mutex)
Definition: threads.cc:396
void BLI_thread_queue_free(ThreadQueue *queue)
Definition: threads.cc:657
void BLI_threadpool_end(struct ListBase *threadbase)
Definition: threads.cc:289
void BLI_thread_queue_nowait(ThreadQueue *queue)
Definition: threads.cc:795
void BLI_thread_queue_wait_finish(ThreadQueue *queue)
Definition: threads.cc:806
void BLI_threadpool_clear(struct ListBase *threadbase)
Definition: threads.cc:278
int BLI_available_threads(struct ListBase *threadbase)
Definition: threads.cc:193
void * BLI_thread_queue_pop(ThreadQueue *queue)
Definition: threads.cc:680
void BLI_threadpool_insert(struct ListBase *threadbase, void *callerdata)
Definition: threads.cc:239
ThreadQueue * BLI_thread_queue_init(void)
Definition: threads.cc:643
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.
_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:50
~Task()
Definition: task_pool.cc:63
bool free_taskdata
Definition: task_pool.cc:51
Task & operator=(Task &&other)=delete
Task(Task &&other)
Definition: task_pool.cc:79
Task(const Task &other)=delete
TaskPool * pool
Definition: task_pool.cc:48
TaskRunFunction run
Definition: task_pool.cc:49
void operator()() const
Definition: task_pool.cc:115
TaskFreeFunction freedata
Definition: task_pool.cc:52
Task(TaskPool *pool, TaskRunFunction run, void *taskdata, bool free_taskdata, TaskFreeFunction freedata)
Definition: task_pool.cc:54
void(* MEM_freeN)(void *vmemh)
Definition: mallocn.c:41
void *(* MEM_callocN)(size_t len, const char *str)
Definition: mallocn.c:45
void *(* MEM_mallocN)(size_t len, const char *str)
Definition: mallocn.c:47
struct blender::compositor::@172::@174 task
ThreadQueue * background_queue
Definition: task_pool.cc:179
bool use_threads
Definition: task_pool.cc:165
volatile bool is_suspended
Definition: task_pool.cc:174
tbb::task_group tbb_group
Definition: util_task.h:67
void * userdata
Definition: task_pool.cc:168
ThreadMutex user_mutex
Definition: task_pool.cc:167
TaskPoolType type
Definition: task_pool.cc:164
ListBase background_threads
Definition: task_pool.cc:178
volatile bool background_is_canceling
Definition: task_pool.cc:180
BLI_mempool * suspended_mempool
Definition: task_pool.cc:175
static void tbb_task_pool_create(TaskPool *pool, TaskPriority priority)
Definition: task_pool.cc:191
TaskPool * BLI_task_pool_create_background(void *userdata, TaskPriority priority)
Definition: task_pool.cc:423
static void tbb_task_pool_run(TaskPool *pool, Task &&task)
Definition: task_pool.cc:207
static bool background_task_pool_canceled(TaskPool *pool)
Definition: task_pool.cc:353
static bool tbb_task_pool_canceled(TaskPool *pool)
Definition: task_pool.cc:270
static void tbb_task_pool_cancel(TaskPool *pool)
Definition: task_pool.cc:258
static void * background_task_run(void *userdata)
Definition: task_pool.cc:300
static void background_task_pool_run(TaskPool *pool, Task &&task)
Definition: task_pool.cc:317
static void background_task_pool_work_and_wait(TaskPool *pool)
Definition: task_pool.cc:328
void * BLI_task_pool_user_data(TaskPool *pool)
Definition: task_pool.cc:541
bool BLI_task_pool_current_canceled(TaskPool *pool)
Definition: task_pool.cc:526
void BLI_task_pool_work_and_wait(TaskPool *pool)
Definition: task_pool.cc:496
void BLI_task_pool_cancel(TaskPool *pool)
Definition: task_pool.cc:511
TaskPool * BLI_task_pool_create_background_serial(void *userdata, TaskPriority priority)
Definition: task_pool.cc:451
static void background_task_pool_free(TaskPool *pool)
Definition: task_pool.cc:358
static void tbb_task_pool_work_and_wait(TaskPool *pool)
Definition: task_pool.cc:233
ThreadMutex * BLI_task_pool_user_mutex(TaskPool *pool)
Definition: task_pool.cc:546
TaskPool * BLI_task_pool_create(void *userdata, TaskPriority priority)
Definition: task_pool.cc:406
static void background_task_pool_create(TaskPool *pool)
Definition: task_pool.cc:311
static void tbb_task_pool_free(TaskPool *pool)
Definition: task_pool.cc:283
TaskPool * BLI_task_pool_create_no_threads(void *userdata)
Definition: task_pool.cc:442
TaskPool * BLI_task_pool_create_suspended(void *userdata, TaskPriority priority)
Definition: task_pool.cc:433
static void background_task_pool_cancel(TaskPool *pool)
Definition: task_pool.cc:337
void BLI_task_pool_free(TaskPool *pool)
Definition: task_pool.cc:456
TaskPoolType
Definition: task_pool.cc:155
@ TASK_POOL_TBB
Definition: task_pool.cc:156
@ TASK_POOL_TBB_SUSPENDED
Definition: task_pool.cc:157
@ TASK_POOL_NO_THREADS
Definition: task_pool.cc:158
@ TASK_POOL_BACKGROUND
Definition: task_pool.cc:159
@ TASK_POOL_BACKGROUND_SERIAL
Definition: task_pool.cc:160
static TaskPool * task_pool_create_ex(void *userdata, TaskPoolType type, TaskPriority priority)
Definition: task_pool.cc:368
void BLI_task_pool_push(TaskPool *pool, TaskRunFunction run, void *taskdata, bool free_taskdata, TaskFreeFunction freedata)
Definition: task_pool.cc:475
function< void(void)> TaskRunFunction
Definition: util_task.h:29