JobSystem: reduce parallel_for overhead
We reuse the current job for the right side instead of spawning a new job, which has overhead. This also cuts in half the number of times we need to copy the user functor.
This commit is contained in:
committed by
Mathias Agopian
parent
4a269e37c5
commit
5e2f73c74e
@@ -472,15 +472,8 @@ struct ParallelForJobData {
|
||||
void parallelWithJobs(JobSystem& js, JobSystem::Job* parent) noexcept {
|
||||
assert(parent);
|
||||
|
||||
// We first split about the number of threads we have, and only then we split the rest
|
||||
// in a single thread (but execute the final cut in new jobs, see parallel() below),
|
||||
// this way we save a lot of copies of JobData and miss-predicted branches
|
||||
if (splits == js.getParallelSplitCount()) {
|
||||
parallel(js, parent);
|
||||
return;
|
||||
}
|
||||
|
||||
// this branch is often miss-predicted (it both sides happen 50% of the calls)
|
||||
right_side:
|
||||
if (splitter.split(splits, count)) {
|
||||
const size_type lc = count / 2;
|
||||
JobData ld(start, lc, splits + uint8_t(1), functor, splitter);
|
||||
@@ -494,19 +487,13 @@ struct ParallelForJobData {
|
||||
// of job creation failure -- rare, but still.
|
||||
js.run(l);
|
||||
|
||||
const size_type rc = count - lc;
|
||||
JobData rd(start + lc, rc, splits + uint8_t(1), functor, splitter);
|
||||
JobSystem::Job* r = js.createJob<JobData, &JobData::parallelWithJobs>(parent, std::move(rd));
|
||||
if (UTILS_UNLIKELY(r == nullptr)) {
|
||||
// couldn't allocate right side job, execute it right now
|
||||
start += lc;
|
||||
count = rc;
|
||||
goto execute;
|
||||
}
|
||||
// don't spawn a job for the right side, just reuse us -- spawning jobs is more
|
||||
// costly than we'd like.
|
||||
start += lc;
|
||||
count -= lc;
|
||||
++splits;
|
||||
goto right_side;
|
||||
|
||||
// All good, execute the right side, but don't signal it,
|
||||
// so it's more likely to be executed next on the same thread
|
||||
js.run(r);
|
||||
} else {
|
||||
execute:
|
||||
// we're done splitting, do the real work here!
|
||||
@@ -515,43 +502,6 @@ execute:
|
||||
}
|
||||
|
||||
private:
|
||||
void parallel(JobSystem& js, JobSystem::Job* parent) noexcept {
|
||||
|
||||
// figure out how many splits we need
|
||||
size_type c = count;
|
||||
uint8_t s = splits;
|
||||
while (splitter.split(s, c)) {
|
||||
c /= 2u;
|
||||
++s;
|
||||
}
|
||||
|
||||
// then linearly create all jobs with number of elements required by the splitter
|
||||
JobSystem::Job* job = nullptr;
|
||||
auto& func = functor;
|
||||
size_type const first = start;
|
||||
size_type const end = first + count;
|
||||
size_type curr = first;
|
||||
|
||||
while (curr + 2u * c < end) {
|
||||
// this creates jobs from the end of the buffer because the WorkStealingDequeue
|
||||
// is a LIFO, this could help streaming to the d-cache.
|
||||
const size_type pos = end - (curr - first) - c;
|
||||
job = js.createJob(parent, [func, pos, c](JobSystem&, JobSystem::Job*) {
|
||||
func(pos, c);
|
||||
});
|
||||
if (UTILS_UNLIKELY(!job)) {
|
||||
goto finish; // oops, no more job available
|
||||
}
|
||||
js.run(job);
|
||||
curr += c;
|
||||
}
|
||||
finish:
|
||||
assert(end >= curr);
|
||||
assert(end - curr >= c);
|
||||
js.signal();
|
||||
functor(start, end - curr);
|
||||
}
|
||||
|
||||
size_type start; // 4
|
||||
size_type count; // 4
|
||||
Functor functor; // ?
|
||||
|
||||
Reference in New Issue
Block a user