Skip to content

Commit

Permalink
PERF: Replace std::function w/ PackagedTaskFunction in ThreadPool queue
Browse files Browse the repository at this point in the history
Using the handmade `PackagedTaskFunction` class may be faster, because it
allowed creating the std::packaged_task on the stack, instead of by calling
`std::make_shared<std::packaged_task<return_type()>>`.
  • Loading branch information
N-Dekker committed Oct 3, 2023
1 parent 75b9967 commit 745b9db
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 5 deletions.
79 changes: 75 additions & 4 deletions Modules/Core/Common/include/itkThreadPool.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ auto result = pool->AddWork([](int param) { return param; }, 7);
{
using return_type = std::invoke_result_t<Function, Arguments...>;

auto task = std::make_shared<std::packaged_task<return_type()>>(
std::packaged_task<return_type()> task(
[function, arguments...]() -> return_type { return function(arguments...); });

std::future<return_type> res = task->get_future();
std::future<return_type> res = task.get_future();
{
const std::lock_guard<std::mutex> lockGuard(this->GetMutex());
m_WorkQueue.emplace_back([task]() { (*task)(); });
m_WorkQueue.emplace_back(std::move(task));
}
m_Condition.notify_one();
return res;
Expand Down Expand Up @@ -147,10 +147,81 @@ auto result = pool->AddWork([](int param) { return param; }, 7);
/** Only used to synchronize the global variable across static libraries.*/
itkGetGlobalDeclarationMacro(ThreadPoolGlobals, PimplGlobals);

// Wraps a `std::packaged_task<T()>` and exposes its function object interface. Internal, for implementation only.
class PackagedTaskFunction
{
private:
class AbstractTaskHolder
{
public:
ITK_DISALLOW_COPY_AND_MOVE(AbstractTaskHolder);

virtual void
Call() = 0;

virtual ~AbstractTaskHolder() = default;

protected:
AbstractTaskHolder() = default;
};

template <typename TReturn>
class TaskHolder : public AbstractTaskHolder
{
public:
ITK_DISALLOW_COPY_AND_MOVE(TaskHolder);

explicit TaskHolder(std::packaged_task<TReturn()> && task)
: m_Task(std::move(task))
{}

~TaskHolder() final = default;

private:
void
Call() final
{
m_Task();
}

std::packaged_task<TReturn()> m_Task{};
};

public:
// Explicit constructor.
template <typename TReturn>
explicit PackagedTaskFunction(std::packaged_task<TReturn()> && packagedTask)
: m_TaskHolder(std::make_unique<TaskHolder<TReturn>>(std::move(packagedTask)))
{}

// Function-call operator.
void
operator()()
{
return m_TaskHolder->Call();
}

// Default-constructor and destructor.
PackagedTaskFunction() = default;
~PackagedTaskFunction() = default;

// Allow move, but disallow copying:
PackagedTaskFunction(const PackagedTaskFunction &) = delete;
PackagedTaskFunction(PackagedTaskFunction &&) = default;
PackagedTaskFunction &
operator=(const PackagedTaskFunction &) = delete;
PackagedTaskFunction &
operator=(PackagedTaskFunction &&) = default;

private:
std::unique_ptr<AbstractTaskHolder> m_TaskHolder{};
};


/** This is a list of jobs submitted to the thread pool.
* This is the only place where the jobs are submitted.
* Filled by AddWork, emptied by ThreadExecute. */
std::deque<std::function<void()>> m_WorkQueue; // guarded by m_PimplGlobals->m_Mutex
std::deque<PackagedTaskFunction> m_WorkQueue; // guarded by m_PimplGlobals->m_Mutex

/** When a thread is idle, it is waiting on m_Condition.
* AddWork signals it to resume a (random) thread. */
Expand Down
2 changes: 1 addition & 1 deletion Modules/Core/Common/src/itkThreadPool.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ ThreadPool::ThreadExecute()

while (true)
{
std::function<void()> task;
PackagedTaskFunction task;

{
std::unique_lock<std::mutex> mutexHolder(m_PimplGlobals->m_Mutex);
Expand Down

0 comments on commit 745b9db

Please sign in to comment.