Skip to content

Commit 372a15e

Browse files
SanderMullerclaude
andcommitted
Never schedule fewer workers than the file count already justifies
sqrt(files) dips below the existing job-count formula between roughly 400 and 800 files, so the rule quietly took workers away from large runs. On 600 warm files it scheduled 12 where the default schedules 14, and measured 13.2% slower against a 2.2% control. The rule exists to stop small runs being starved, never to reduce a large one. Take the maximum of the two, so the adaptive count can only ever raise parallelism. Small runs are unchanged: 9, 25, 50, 100 and 200 files still schedule 2, 3, 4, 5 and 7 workers. The test asserts the invariant across 13 file counts rather than trusting a table, because the dip only shows up in a band neither end of the range covers. Reported by @staabm on the pull request. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent b45102f commit 372a15e

2 files changed

Lines changed: 27 additions & 3 deletions

File tree

‎src/Parallel/Scheduler.php‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public function scheduleWork(
9191

9292
$desiredNumberOfProcesses = null;
9393
if ($this->adaptiveParallelWorkerCount) {
94-
$desiredNumberOfProcesses = $this->resolveDesiredNumberOfProcesses(count($files), $cpuCores);
94+
$desiredNumberOfProcesses = $this->resolveDesiredNumberOfProcesses(count($files), $numberOfJobs, $cpuCores);
9595

9696
// the spawn loop stops when the queue runs dry, so a worker without a
9797
// job of its own never starts - chunk finely enough to feed them all
@@ -133,7 +133,7 @@ public function scheduleWork(
133133
*
134134
* @return positive-int
135135
*/
136-
private function resolveDesiredNumberOfProcesses(int $numberOfFiles, int $cpuCores): int
136+
private function resolveDesiredNumberOfProcesses(int $numberOfFiles, int $numberOfJobs, int $cpuCores): int
137137
{
138138
if ($numberOfFiles < 1) {
139139
return 1;
@@ -142,7 +142,12 @@ private function resolveDesiredNumberOfProcesses(int $numberOfFiles, int $cpuCor
142142
$floor = $numberOfFiles >= self::ADAPTIVE_SECOND_WORKER_FILE_THRESHOLD ? 2 : 1;
143143
$fromFileCount = (int) round(self::ADAPTIVE_WORKERS_PER_SQRT_FILE * sqrt($numberOfFiles));
144144

145-
return max(1, min(max($floor, $fromFileCount), $cpuCores, $numberOfFiles));
145+
// never below what the job count alone already justifies: the point is to stop
146+
// small runs being starved, not to take workers away from large ones, and
147+
// sqrt() dips under the existing formula between roughly 400 and 800 files
148+
$fromJobCount = max((int) floor($numberOfJobs / $this->minimumNumberOfJobsPerProcess), 1);
149+
150+
return max(1, min(max($floor, $fromFileCount, $fromJobCount), $cpuCores, $numberOfFiles));
146151
}
147152

148153
/**

‎tests/PHPStan/Parallel/SchedulerTest.php‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,25 @@ public function testAdaptiveWorkerCountKeepsTinyRunsSerial(): void
231231
$this->assertSame(2, $scheduler->scheduleWork(14, array_fill(0, 9, 'file.php'), $callback)->getNumberOfProcesses());
232232
}
233233

234+
public function testAdaptiveWorkerCountIsNeverBelowTheDefault(): void
235+
{
236+
// the rule exists to stop small runs being starved, never to take workers away
237+
// from large ones - sqrt() alone dips under the existing formula between roughly
238+
// 400 and 800 files, which measured 13% slower at 600
239+
$callback = static fn (string $file): int => 0;
240+
foreach ([1, 5, 9, 25, 50, 100, 200, 300, 400, 600, 800, 1424, 4524] as $numberOfFiles) {
241+
$files = array_fill(0, $numberOfFiles, 'file.php');
242+
$legacy = (new Scheduler(20, Scheduler::AUTO, 2))->scheduleWork(14, $files, $callback);
243+
$adaptive = (new Scheduler(20, Scheduler::AUTO, 2, true))->scheduleWork(14, $files, $callback);
244+
245+
$this->assertGreaterThanOrEqual(
246+
$legacy->getNumberOfProcesses(),
247+
$adaptive->getNumberOfProcesses(),
248+
sprintf('%d files', $numberOfFiles),
249+
);
250+
}
251+
}
252+
234253
public function testAdaptiveWorkerCountNeverExceedsTheJobCount(): void
235254
{
236255
// a worker with no job never starts, so the schedule must not claim one

0 commit comments

Comments
 (0)