dougbinks / enkiTS

A permissively licensed C and C++ Task Scheduler for creating parallel programs. Requires C++11 support.
zlib License
1.66k stars 138 forks source link

Scheduling tasks with high priority after-the-fact #77

Open baldurk opened 1 year ago

baldurk commented 1 year ago

This may be out of scope for the library, or potentially I've missed a way to do it. What I would like to be able to do is add a number of tasks with no particular priority and have them be scheduled in parallel in no particular order, but I would want the ability to immediately run one if I discover that it's particularly needed soon (while allowing others to still be scheduled as/when).

The sketched idea is to have the main thread push tasks for a bunch of deferred operations as it's processing something, but then if the results of one of those deferred operations is needed it wants to either immediately run that task on the main thread or spinloop if it's already been scheduled. The tasks can come in any order so you could push 100 different tasks and then want the results from number 50 before continuing, so waiting for 1-49 would not be ideal as then the main thread can't continue processing in parallel with them.

As far as I can tell unless I'm misunderstanding the code, WaitForTask does not prioritise the task you pass in to wait on but it looks at all tasks at equal priority. I don't see a way to elevate the priority of a task after it has been created either. I also wondered if the way to solve this would be to add a new task with a dependency on the one I want to run, but at least from a layperson's eye that doesn't seem like it would change the scheduling order.

Is there a way to do this, and/or is it something you'd be interested in supporting? In principle this could be refactored such that there is no main thread needing to sync, and everything including its processing becomes task-based so this translates into dependency management, but that's further than I'd want to go.

dougbinks commented 1 year ago

I'll have a think about this, but the following is how I would manage it with the current scheduler:

  1. Add an atomic int flag to the task data to allow the task to be 'discarded' - i.e. in the ExecuteRange function it exits when this is set.
  2. To change a task priority cancel the old task and launch a new one.

WaitForTask can be made to prioritise tasks above a certain priority by setting the priorityOfLowestToRun_ parameter to that priority. So you could launch all tasks at a low priority, and when you need a result use the above approach to boost to the highest priority then use WaitForTask with priorityOfLowestToRun_ set to that.

baldurk commented 1 year ago

That's a good idea, that should work for what I want - I had thought about using priorities wait WaitForTask but didn't think about a cancel+re-execute approach.

dougbinks commented 1 year ago

If you can keep track of any work done you can also only process what's needed in the new task, but it might be simpler just to discard it depending on what you're doing.

dougbinks commented 1 year ago

I can think of a way to do this in enkiTS, with a little extra work. Elevating a tasks priority would be fairly expensive, but it could be done. The work the threads are already doing cannot currently be interrupted, so this wouldn't guarantee that it would get processed immediately but it would be able to move it to the front of all other tasks or subtasks (the internal unit of work) which had not yet started processing. I'll think a bit more before committing to writing any code and get back to you.

baldurk commented 1 year ago

That totally makes sense, it's co-operative rather than pre-emptive after all. I was only after an "as soon as possible" scheduling rather than immediate. For my part though I think your suggestion of cancelling and re-queuing with high priority is sufficient, so if this would be expensive or awkward to implement then feel free to close.