Skip to content

v1.10 Wait For Pinned Tasks

Compare
Choose a tag to compare
@dougbinks dougbinks released this 20 Jul 10:55
· 82 commits to master since this release
7e9a83a

This release adds a major new feature - WaitForPinnedTasks - suitable for performing work which could be OS blocking such as IO.

The mortification for this feature is that Some calls, such as file and network IO, may result in the thread being blocked whilst waiting on an external event which does not consume CPU resources to occur. If this was run on a standard enkiTS thread the task scheduler would have fewer threads able to perform computational work. Creating more threads than CPU cores offers one solution, but this will result in OS scheduling overhead which we wish to minimize. The WaitForPinnedTasks() function permits an enkiTS thread to block at the OS level until it explicitly receives new work via a PinnedTask. Developers can thus create extra threads which then loop calling WaitForPinnedTasks() and RunPinnedTasks() to perform IO/blocking work, minimizing OS scheduling overhead whilst keeping all CPU cores active with enkiTS threads.

WaitForPinnedTasks thread usage in C++:

#include "TaskScheduler.h"

enki::TaskScheduler g_TS;

struct RunPinnedTaskLoopTask : enki::IPinnedTask
{
    void Execute() override
    {
        while( g_TS.GetIsRunning() )
        {
            g_TS.WaitForNewPinnedTasks(); // this thread will 'sleep' until there are new pinned tasks
            g_TS.RunPinnedTasks();
        }
    }
};

struct PretendDoFileIO : enki::IPinnedTask
{
    void Execute() override
    {
        // Do file IO
    }
};

int main(int argc, const char * argv[])
{
    enki::TaskSchedulerConfig config;

    // In this example we create more threads than the hardware can run,
    // because the IO thread will spend most of it's time idle or blocked
    // and therefore not scheduled for CPU time by the OS
    config.numTaskThreadsToCreate += 1;

    g_TS.Initialize( config );

    // in this example we place our IO threads at the end
    RunPinnedTaskLoopTask runPinnedTaskLoopTasks;
    runPinnedTaskLoopTasks.threadNum = g_TS.GetNumTaskThreads() - 1;
    g_TS.AddPinnedTask( &runPinnedTaskLoopTasks );

    // Send pretend file IO task to external thread FILE_IO
    PretendDoFileIO pretendDoFileIO;
    pretendDoFileIO.threadNum = runPinnedTaskLoopTasks.threadNum;
    g_TS.AddPinnedTask( &pretendDoFileIO );

    // ensure runPinnedTaskLoopTasks complete by explicitly calling shutdown
    g_TS.WaitforAllAndShutdown();

    return 0;
}

enkiTSMicroprofileExample
Screenshot of enkiTS in action in Avoyd Voxel Editor whilst CPU path tracing a scene. Avoyd uses additional enkITS threads using WaitForNewPinnedTasks() to wait for PinnedTasks which perform blocking IO (not shown in profile as these are hard to capture in a nice looking screenshot).

Support development of enkiTS through Github Sponsors or Patreon

Become a Patron