Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenlib/intern/task_pool.cc
| Show All 16 Lines | |||||
| /** \file | /** \file | ||||
| * \ingroup bli | * \ingroup bli | ||||
| * | * | ||||
| * Task pool to run tasks in parallel. | * Task pool to run tasks in parallel. | ||||
| */ | */ | ||||
| #include <cstdlib> | #include <cstdlib> | ||||
| #include <memory> | #include <memory> | ||||
| #include <thread> | |||||
| #include <utility> | #include <utility> | ||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| #include "DNA_listBase.h" | #include "DNA_listBase.h" | ||||
| #include "BLI_math.h" | #include "BLI_math.h" | ||||
| #include "BLI_mempool.h" | #include "BLI_mempool.h" | ||||
| ▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | #if defined(WITH_TBB) && TBB_INTERFACE_VERSION_MAJOR < 10 | ||||
| } | } | ||||
| #else | #else | ||||
| Task(const Task &other) = delete; | Task(const Task &other) = delete; | ||||
| #endif | #endif | ||||
| Task &operator=(const Task &other) = delete; | Task &operator=(const Task &other) = delete; | ||||
| Task &operator=(Task &&other) = delete; | Task &operator=(Task &&other) = delete; | ||||
| /* Execute task. */ | void operator()() const; | ||||
| void operator()() const | |||||
| { | |||||
| #ifdef WITH_TBB | |||||
| tbb::this_task_arena::isolate([this] { run(pool, taskdata); }); | |||||
| #else | |||||
| run(pool, taskdata); | |||||
| #endif | |||||
| } | |||||
| }; | }; | ||||
| /* TBB Task Group. | /* TBB Task Group. | ||||
| * | * | ||||
| * Subclass since there seems to be no other way to set priority. */ | * Subclass since there seems to be no other way to set priority. */ | ||||
| #ifdef WITH_TBB | #ifdef WITH_TBB | ||||
| class TBBTaskGroup : public tbb::task_group { | class TBBTaskGroup : public tbb::task_group { | ||||
| Show All 27 Lines | enum TaskPoolType { | ||||
| TASK_POOL_NO_THREADS, | TASK_POOL_NO_THREADS, | ||||
| TASK_POOL_BACKGROUND, | TASK_POOL_BACKGROUND, | ||||
| TASK_POOL_BACKGROUND_SERIAL, | TASK_POOL_BACKGROUND_SERIAL, | ||||
| }; | }; | ||||
| struct TaskPool { | struct TaskPool { | ||||
| TaskPoolType type; | TaskPoolType type; | ||||
| bool use_threads; | bool use_threads; | ||||
| TaskIsolation task_isolation; | |||||
| ThreadMutex user_mutex; | ThreadMutex user_mutex; | ||||
| void *userdata; | void *userdata; | ||||
| /* TBB task pool. */ | |||||
| #ifdef WITH_TBB | #ifdef WITH_TBB | ||||
| /* TBB task pool. */ | |||||
| TBBTaskGroup tbb_group; | TBBTaskGroup tbb_group; | ||||
| /* This is used to detect a common way to accidentally create a deadlock with task isolation. */ | |||||
| std::thread::id task_pool_create_thread_id; | |||||
| #endif | #endif | ||||
| volatile bool is_suspended; | volatile bool is_suspended; | ||||
| BLI_mempool *suspended_mempool; | BLI_mempool *suspended_mempool; | ||||
| /* Background task pool. */ | /* Background task pool. */ | ||||
| ListBase background_threads; | ListBase background_threads; | ||||
| ThreadQueue *background_queue; | ThreadQueue *background_queue; | ||||
| volatile bool background_is_canceling; | volatile bool background_is_canceling; | ||||
| }; | }; | ||||
| /* Execute task. */ | |||||
| void Task::operator()() const | |||||
| { | |||||
| #ifdef WITH_TBB | |||||
| if (pool->task_isolation == TASK_ISOLATION_ON) { | |||||
| tbb::this_task_arena::isolate([this] { run(pool, taskdata); }); | |||||
| return; | |||||
| } | |||||
| #endif | |||||
| run(pool, taskdata); | |||||
| } | |||||
| static void assert_on_valid_thread(TaskPool *pool) | |||||
| { | |||||
| /* TODO: Remove this `return` to enable the check. */ | |||||
| return; | |||||
| #ifdef DEBUG | |||||
| # ifdef WITH_TBB | |||||
| if (pool->task_isolation == TASK_ISOLATION_ON) { | |||||
| const std::thread::id current_id = std::this_thread::get_id(); | |||||
| /* This task pool is modified from different threads. To avoid deadlocks, `TASK_ISOLATION_OFF` | |||||
| * has to be used. Task isolation can still be used in a more fine-grained way within the | |||||
| * tasks, but should not be enabled for the entire task pool. */ | |||||
| BLI_assert(pool->task_pool_create_thread_id == current_id); | |||||
| } | |||||
| # endif | |||||
| #endif | |||||
| UNUSED_VARS_NDEBUG(pool); | |||||
| } | |||||
| /* TBB Task Pool. | /* TBB Task Pool. | ||||
| * | * | ||||
| * Task pool using the TBB scheduler for tasks. When building without TBB | * Task pool using the TBB scheduler for tasks. When building without TBB | ||||
| * support or running Blender with -t 1, this reverts to single threaded. | * support or running Blender with -t 1, this reverts to single threaded. | ||||
| * | * | ||||
| * Tasks may be suspended until in all are created, to make it possible to | * Tasks may be suspended until in all are created, to make it possible to | ||||
| * initialize data structures and create tasks in a single pass. */ | * initialize data structures and create tasks in a single pass. */ | ||||
| ▲ Show 20 Lines • Show All 169 Lines • ▼ Show 20 Lines | static void background_task_pool_free(TaskPool *pool) | ||||
| background_task_pool_work_and_wait(pool); | background_task_pool_work_and_wait(pool); | ||||
| BLI_threadpool_end(&pool->background_threads); | BLI_threadpool_end(&pool->background_threads); | ||||
| BLI_thread_queue_free(pool->background_queue); | BLI_thread_queue_free(pool->background_queue); | ||||
| } | } | ||||
| /* Task Pool */ | /* Task Pool */ | ||||
| static TaskPool *task_pool_create_ex(void *userdata, TaskPoolType type, TaskPriority priority) | static TaskPool *task_pool_create_ex(void *userdata, | ||||
| TaskPoolType type, | |||||
| TaskPriority priority, | |||||
| TaskIsolation task_isolation) | |||||
| { | { | ||||
| const bool use_threads = BLI_task_scheduler_num_threads() > 1 && type != TASK_POOL_NO_THREADS; | const bool use_threads = BLI_task_scheduler_num_threads() > 1 && type != TASK_POOL_NO_THREADS; | ||||
| /* Background task pool uses regular TBB scheduling if available. Only when | /* Background task pool uses regular TBB scheduling if available. Only when | ||||
| * building without TBB or running with -t 1 do we need to ensure these tasks | * building without TBB or running with -t 1 do we need to ensure these tasks | ||||
| * do not block the main thread. */ | * do not block the main thread. */ | ||||
| if (type == TASK_POOL_BACKGROUND && use_threads) { | if (type == TASK_POOL_BACKGROUND && use_threads) { | ||||
| type = TASK_POOL_TBB; | type = TASK_POOL_TBB; | ||||
| } | } | ||||
| /* Allocate task pool. */ | /* Allocate task pool. */ | ||||
| TaskPool *pool = (TaskPool *)MEM_callocN(sizeof(TaskPool), "TaskPool"); | TaskPool *pool = (TaskPool *)MEM_callocN(sizeof(TaskPool), "TaskPool"); | ||||
| pool->type = type; | pool->type = type; | ||||
| pool->use_threads = use_threads; | pool->use_threads = use_threads; | ||||
| pool->task_isolation = task_isolation; | |||||
| #ifdef WITH_TBB | |||||
| pool->task_pool_create_thread_id = std::this_thread::get_id(); | |||||
| #endif | |||||
| pool->userdata = userdata; | pool->userdata = userdata; | ||||
| BLI_mutex_init(&pool->user_mutex); | BLI_mutex_init(&pool->user_mutex); | ||||
| switch (type) { | switch (type) { | ||||
| case TASK_POOL_TBB: | case TASK_POOL_TBB: | ||||
| case TASK_POOL_TBB_SUSPENDED: | case TASK_POOL_TBB_SUSPENDED: | ||||
| case TASK_POOL_NO_THREADS: | case TASK_POOL_NO_THREADS: | ||||
| tbb_task_pool_create(pool, priority); | tbb_task_pool_create(pool, priority); | ||||
| break; | break; | ||||
| case TASK_POOL_BACKGROUND: | case TASK_POOL_BACKGROUND: | ||||
| case TASK_POOL_BACKGROUND_SERIAL: | case TASK_POOL_BACKGROUND_SERIAL: | ||||
| background_task_pool_create(pool); | background_task_pool_create(pool); | ||||
| break; | break; | ||||
| } | } | ||||
| return pool; | return pool; | ||||
| } | } | ||||
| /** | /** | ||||
| * Create a normal task pool. Tasks will be executed as soon as they are added. | * Create a normal task pool. Tasks will be executed as soon as they are added. | ||||
| */ | */ | ||||
| TaskPool *BLI_task_pool_create(void *userdata, TaskPriority priority) | TaskPool *BLI_task_pool_create(void *userdata, TaskPriority priority, TaskIsolation task_isolation) | ||||
| { | { | ||||
| return task_pool_create_ex(userdata, TASK_POOL_TBB, priority); | return task_pool_create_ex(userdata, TASK_POOL_TBB, priority, task_isolation); | ||||
| } | } | ||||
| /** | /** | ||||
| * Create a background task pool. | * Create a background task pool. | ||||
| * In multi-threaded context, there is no differences with #BLI_task_pool_create(), | * In multi-threaded context, there is no differences with #BLI_task_pool_create(), | ||||
| * but in single-threaded case it is ensured to have at least one worker thread to run on | * but in single-threaded case it is ensured to have at least one worker thread to run on | ||||
| * (i.e. you don't have to call #BLI_task_pool_work_and_wait | * (i.e. you don't have to call #BLI_task_pool_work_and_wait | ||||
| * on it to be sure it will be processed). | * on it to be sure it will be processed). | ||||
| * | * | ||||
| * \note Background pools are non-recursive | * \note Background pools are non-recursive | ||||
| * (that is, you should not create other background pools in tasks assigned to a background pool, | * (that is, you should not create other background pools in tasks assigned to a background pool, | ||||
| * they could end never being executed, since the 'fallback' background thread is already | * they could end never being executed, since the 'fallback' background thread is already | ||||
| * busy with parent task in single-threaded context). | * busy with parent task in single-threaded context). | ||||
| */ | */ | ||||
| TaskPool *BLI_task_pool_create_background(void *userdata, TaskPriority priority) | TaskPool *BLI_task_pool_create_background(void *userdata, | ||||
| TaskPriority priority, | |||||
| TaskIsolation task_isolation) | |||||
| { | { | ||||
| return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND, priority); | return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND, priority, task_isolation); | ||||
| } | } | ||||
| /** | /** | ||||
| * Similar to BLI_task_pool_create() but does not schedule any tasks for execution | * Similar to BLI_task_pool_create() but does not schedule any tasks for execution | ||||
| * for until BLI_task_pool_work_and_wait() is called. This helps reducing threading | * for until BLI_task_pool_work_and_wait() is called. This helps reducing threading | ||||
| * overhead when pushing huge amount of small initial tasks from the main thread. | * overhead when pushing huge amount of small initial tasks from the main thread. | ||||
| */ | */ | ||||
| TaskPool *BLI_task_pool_create_suspended(void *userdata, TaskPriority priority) | TaskPool *BLI_task_pool_create_suspended(void *userdata, | ||||
| TaskPriority priority, | |||||
| TaskIsolation task_isolation) | |||||
| { | { | ||||
| return task_pool_create_ex(userdata, TASK_POOL_TBB_SUSPENDED, priority); | return task_pool_create_ex(userdata, TASK_POOL_TBB_SUSPENDED, priority, task_isolation); | ||||
| } | } | ||||
| /** | /** | ||||
| * Single threaded task pool that executes pushed task immediately, for | * Single threaded task pool that executes pushed task immediately, for | ||||
| * debugging purposes. | * debugging purposes. | ||||
| */ | */ | ||||
| TaskPool *BLI_task_pool_create_no_threads(void *userdata) | TaskPool *BLI_task_pool_create_no_threads(void *userdata) | ||||
| { | { | ||||
| return task_pool_create_ex(userdata, TASK_POOL_NO_THREADS, TASK_PRIORITY_HIGH); | return task_pool_create_ex( | ||||
| userdata, TASK_POOL_NO_THREADS, TASK_PRIORITY_HIGH, TASK_ISOLATION_ON); | |||||
| } | } | ||||
| /** | /** | ||||
| * Task pool that executes one task after the other, possibly on different threads | * Task pool that executes one task after the other, possibly on different threads | ||||
| * but never in parallel. | * but never in parallel. | ||||
| */ | */ | ||||
| TaskPool *BLI_task_pool_create_background_serial(void *userdata, TaskPriority priority) | TaskPool *BLI_task_pool_create_background_serial(void *userdata, TaskPriority priority) | ||||
| { | { | ||||
| return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND_SERIAL, priority); | return task_pool_create_ex(userdata, TASK_POOL_BACKGROUND_SERIAL, priority, TASK_ISOLATION_ON); | ||||
| } | } | ||||
| void BLI_task_pool_free(TaskPool *pool) | void BLI_task_pool_free(TaskPool *pool) | ||||
| { | { | ||||
| switch (pool->type) { | switch (pool->type) { | ||||
| case TASK_POOL_TBB: | case TASK_POOL_TBB: | ||||
| case TASK_POOL_TBB_SUSPENDED: | case TASK_POOL_TBB_SUSPENDED: | ||||
| case TASK_POOL_NO_THREADS: | case TASK_POOL_NO_THREADS: | ||||
| Show All 11 Lines | |||||
| } | } | ||||
| void BLI_task_pool_push(TaskPool *pool, | void BLI_task_pool_push(TaskPool *pool, | ||||
| TaskRunFunction run, | TaskRunFunction run, | ||||
| void *taskdata, | void *taskdata, | ||||
| bool free_taskdata, | bool free_taskdata, | ||||
| TaskFreeFunction freedata) | TaskFreeFunction freedata) | ||||
| { | { | ||||
| assert_on_valid_thread(pool); | |||||
| Task task(pool, run, taskdata, free_taskdata, freedata); | Task task(pool, run, taskdata, free_taskdata, freedata); | ||||
| switch (pool->type) { | switch (pool->type) { | ||||
| case TASK_POOL_TBB: | case TASK_POOL_TBB: | ||||
| case TASK_POOL_TBB_SUSPENDED: | case TASK_POOL_TBB_SUSPENDED: | ||||
| case TASK_POOL_NO_THREADS: | case TASK_POOL_NO_THREADS: | ||||
| tbb_task_pool_run(pool, std::move(task)); | tbb_task_pool_run(pool, std::move(task)); | ||||
| break; | break; | ||||
| case TASK_POOL_BACKGROUND: | case TASK_POOL_BACKGROUND: | ||||
| case TASK_POOL_BACKGROUND_SERIAL: | case TASK_POOL_BACKGROUND_SERIAL: | ||||
| background_task_pool_run(pool, std::move(task)); | background_task_pool_run(pool, std::move(task)); | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| void BLI_task_pool_work_and_wait(TaskPool *pool) | void BLI_task_pool_work_and_wait(TaskPool *pool) | ||||
| { | { | ||||
| assert_on_valid_thread(pool); | |||||
| switch (pool->type) { | switch (pool->type) { | ||||
| case TASK_POOL_TBB: | case TASK_POOL_TBB: | ||||
| case TASK_POOL_TBB_SUSPENDED: | case TASK_POOL_TBB_SUSPENDED: | ||||
| case TASK_POOL_NO_THREADS: | case TASK_POOL_NO_THREADS: | ||||
| tbb_task_pool_work_and_wait(pool); | tbb_task_pool_work_and_wait(pool); | ||||
| break; | break; | ||||
| case TASK_POOL_BACKGROUND: | case TASK_POOL_BACKGROUND: | ||||
| case TASK_POOL_BACKGROUND_SERIAL: | case TASK_POOL_BACKGROUND_SERIAL: | ||||
| ▲ Show 20 Lines • Show All 44 Lines • Show Last 20 Lines | |||||