Repository navigation
[df] Rework RNTuple MT scheduling with a split work-stealing approach - #23327
Conversation
Test Results 24 files 24 suites 4d 4h 39m 55s ⏱️ For more details on these failures, see this check. Results for commit 20bebc0. ♻️ This comment has been updated with latest results. |
hageboeck
left a comment
There was a problem hiding this comment.
🚀
Here's a first pass with a few suggestions and a few questions.
jblomer
left a comment
There was a problem hiding this comment.
Thank you for this work!
I think it may be easier to follow the code if the lambdas became class methods. I would also make documentation easier. Generally, I think a high-level documentation of the scheduling approach would be helpful.
Regarding the use of TBB: would it be possible/easier to simply use a TTaskGroup?
ece30c1 to
06ccada
Compare
|
HI @hageboeck @jblomer , I have addressed your PR reviews and updated the changes. Documentation has been added throughout, and I've also memorialised the strategy in a separate markdown document as requested by @jblomer . Let me know your thoughts, thanks! |
06ccada to
9acbfde
Compare
da362e3 to
5c79602
Compare
5c79602 to
167b27a
Compare
jblomer
left a comment
There was a problem hiding this comment.
Thanks!
Some first comments on the documentation. I'll review the code later today.
167b27a to
efdf7a8
Compare
|
@jblomer thanks for the review! I have rebased the branch, updated the code to use the new |
jblomer
left a comment
There was a problem hiding this comment.
In principle looks good to me. Some minor comments and one potentially larger questions on the work splitting.
hahnjo
left a comment
There was a problem hiding this comment.
Disclaimer: I've not carefully inspected every line in this PR, so feel free to point out if the following breaks down for very obvious reasons that I'm missing.
From a high level, it seems to me that "forcing" RNTuple scheduling into splittable ranges for consumption by TBB is both complex and limiting at the same time. The main "restriction" that we set ourselves is that we expect a created task A to work on file 1, task B on file 2, etc. I wonder if we can get more flexibility by just picking the next file in each task. This will also allow staging files in the background, done in the current implementation but likely not functional anymore in this PR (to be confirmed).
Roughly something like the following:
- Start the background thread to stage files according to the number of slots.
- Start as many tasks as files.
- Upon execution, pick the next file and extend the sliding window of files to be staged.
- Open the file and start iterating clusters (potentially with another loop for lazy loading of cluster group details).
- When at some point, all queued tasks are executing (easily noticed by all files being picked up), go into tail-scheduling mode: create a (nested) task group and schedule tasks for all remaining clusters. Each cluster-task clones the page source from the "parent" file-task and then works on only that cluster. This should efficiently distribute the remaining work.
The number of open files should be limited to twice the number of slots:
- During staging, one per file-task and one per slot in the background thread.
- In the tail-scheduling mode, one per file-task and one for each executing cluster-task.
The MT scheduling of RNTupleDS is changed. The data source adopts now a similar pattern as TTreeDS, implementing a ProcessMT(ROOT::Detail::RDF::RLoopManager &lm) method. This allows the data source to manage the creation and scheduling of multithreaded tasks, inside of which the loop manager is invoked to apply the computation graph to the range of entries in the task. A brief summary of the scheduling approach follows: 1. One task is launched in parallel per input file 2. At the beginning of the file-based task, a page source is created and the list of clusters is computed a. If the user has not specified a global entry range, it is the actual list of clusters of the page source. This happens in ScheduleTasksForFile. b. If the user has specified a global entry range, before launching the file-based tasks the data source had computed a full list of global entry offsets across all input files. The list of clusters of the page source is then adjusted according to the position of the global entry range w.r.t. the global offsets of this file. This happens in ScheduleTasksForFileWithGlobalOffset. 3. The task goes on within ScheduleTaskForPageSource, which invokes TBB's parallel_for with two arguments, one respecting the Range and the other respecting the Body requirements. See the docs respectively at https://oneapi-spec.uxlfoundation.org/specifications/oneapi/latest/elements/onetbb/source/named_requirements/algorithms/range and https://oneapi-spec.uxlfoundation.org/specifications/oneapi/latest/elements/onetbb/source/named_requirements/algorithms/par_for_body. a. The Range argument is of type RPageSourceSplittableRange, a struct representing a range of entries (N consecutive clusters) tied to a specific page source to be processed. b. The first time parallel_for is invoked, an RPageSourceSplittableRange instance is passed to it which includes the whole list of clusters for that page source. c. TBB immediately calls the Range object's SplitWork method, which will split the cluster range in roughly two equal parts, the first part is re-assigned to the same object, the second part is used to create a new RPageSourceSplittableRange. d. TBB continues to split the task recursively, until a list of all non-splittable tasks is available. e. Only then, the TBB parallel_for will schedule the various calls to the ParallelForBody function, each taking one of the cluster ranges generated before. f. Within ParallelForBody, the computation graph is executed by reading data from the page source, restricting it to the assigned cluster range, by calling RLoopManager::RNTupleThreadTask. This approach delegates to the scheduling engine (TBB in this case) the responsibility to optimise the task processing and split tasks that may be running for too long (e.g. some files may contain many more entries than others). The strategy is also documented in a markdown file for posterity.
efdf7a8 to
20bebc0
Compare
|
Thanks @jblomer for the review. After your comment on the parallel_scheduling.md file, about discussing the strategy to keep the number of open files checked, as well as the question on exactly one TBB may split a Thanks also to @hahnjo for the comment and the insightful conversation we had offline. Following that conversation, I agree that there is a generally more optimised strategy to be devised, which includes re-enabling the page source staging that this PR is currently disabling for the MT case. I would propose to move ahead with the general logic proposed in this PR, as it shows better performance than the one currently available in master, and have this as the new MT strategy for 6.42. As a follow up, I will attempt to implement the strategy proposed by @hahnjo and @jblomer, and further refine it for the following ROOT release so that it will be also comparable with the strategy proposed by this PR. |
|
Fine for me to move forward with this now. I think it would be a good idea to open an issue where we summarize the subsequent planned changes. Regarding the splitting: doesn't this mean that we end up with all tasks processing at most 3 clusters? This would mean that the cluster prefetching does not work very well because, by setting the entry range on the page source, we prevent prefetching across task boundaries. We can probably work around this, but I'm less convinced now that TBB splittable tasks is what we want. I think for the next iteration, we should reconsider a design where we are in control of the exact task creation (e.g. along the lines of what @hahnjo suggests) together with a shared data structure that may require locking at every cluster boundary. |
Yes, exactly. This is the main motivation for the stopping condition given by the
We have discussed a strategy offline somewhat along those lines yes, the finer details will need to be seen in practice. |
Alternative to #22631 , implementing native IMT work-stealing for the RNTuple data source