Skip to content

Commit 5d6ebec

Browse files
AJenboondrejmirtes
authored andcommitted
Implement worker auto-scaler
1 parent 66d9913 commit 5d6ebec

4 files changed

Lines changed: 59 additions & 8 deletions

File tree

‎conf/config.neon‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ parameters:
107107
parallel:
108108
jobSize: 20
109109
processTimeout: 600.0
110-
maximumNumberOfProcesses: 8
110+
maximumNumberOfProcesses: auto
111111
minimumNumberOfJobsPerProcess: 2
112112
buffer: 134217728 # 128 MB
113113
loadLimit: 1.0

‎conf/parametersSchema.neon‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ parametersSchema:
109109
parallel: structure([
110110
jobSize: int(),
111111
processTimeout: float(),
112-
maximumNumberOfProcesses: int(),
112+
maximumNumberOfProcesses: anyOf(int(), 'auto'),
113113
minimumNumberOfJobsPerProcess: int(),
114114
buffer: int(),
115115
loadLimit: schema(float(), nullable())

‎src/Parallel/Scheduler.php‎

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,21 @@
1919
final class Scheduler implements DiagnoseExtension
2020
{
2121

22-
/** @var array{int, int, int, int}|null */
22+
public const AUTO = 'auto';
23+
24+
/** @var array{int, int, int, int, string}|null */
2325
private ?array $storedData = null;
2426

2527
/**
2628
* @param positive-int $jobSize
27-
* @param positive-int $maximumNumberOfProcesses
29+
* @param positive-int|self::AUTO $maximumNumberOfProcesses
2830
* @param positive-int $minimumNumberOfJobsPerProcess
2931
*/
3032
public function __construct(
3133
#[AutowiredParameter(ref: '%parallel.jobSize%')]
3234
private int $jobSize,
3335
#[AutowiredParameter(ref: '%parallel.maximumNumberOfProcesses%')]
34-
private int $maximumNumberOfProcesses,
36+
private int|string $maximumNumberOfProcesses,
3537
#[AutowiredParameter(ref: '%parallel.minimumNumberOfJobsPerProcess%')]
3638
private int $minimumNumberOfJobsPerProcess,
3739
)
@@ -78,25 +80,46 @@ public function scheduleWork(
7880
$cpuCores,
7981
);
8082

81-
$usedNumberOfProcesses = min($numberOfProcesses, $this->maximumNumberOfProcesses);
82-
$this->storedData = [$cpuCores, count($files), count($jobs), $usedNumberOfProcesses];
83+
[$maximumNumberOfProcesses, $decision] = $this->resolveMaximumNumberOfProcesses($cpuCores);
84+
$usedNumberOfProcesses = min($numberOfProcesses, $maximumNumberOfProcesses);
85+
$this->storedData = [$cpuCores, count($files), count($jobs), $usedNumberOfProcesses, $decision];
8386

8487
return new Schedule($usedNumberOfProcesses, $jobs);
8588
}
8689

90+
/**
91+
* How many workers may run at once, and a human-readable account of why - which
92+
* `diagnose` prints, because a user who thinks the number is wrong needs to see
93+
* which input produced it.
94+
*
95+
* @return array{positive-int, string}
96+
*/
97+
private function resolveMaximumNumberOfProcesses(int $cpuCores): array
98+
{
99+
if ($this->maximumNumberOfProcesses !== self::AUTO) {
100+
return [$this->maximumNumberOfProcesses, 'configured'];
101+
}
102+
103+
return [
104+
max(1, $cpuCores),
105+
sprintf('auto, limited by %d usable CPU cores', $cpuCores),
106+
];
107+
}
108+
87109
public function print(Output $output): void
88110
{
89111
if ($this->storedData === null) {
90112
return;
91113
}
92114

93-
[$cpuCores, $filesCount, $jobsCount, $usedNumberOfProcesses] = $this->storedData;
115+
[$cpuCores, $filesCount, $jobsCount, $usedNumberOfProcesses, $decision] = $this->storedData;
94116

95117
$output->writeLineFormatted('<info>Parallel processing scheduler:</info>');
96118
$output->writeLineFormatted(sprintf('# of detected CPU cores: %d', $cpuCores));
97119
$output->writeLineFormatted(sprintf('# of analysed files: %d', $filesCount));
98120
$output->writeLineFormatted(sprintf('# of jobs: %d', $jobsCount));
99121
$output->writeLineFormatted(sprintf('# of spawned processes: %d', $usedNumberOfProcesses));
122+
$output->writeLineFormatted(sprintf('Process limit: %s', $decision));
100123
$output->writeLineFormatted('');
101124
}
102125

‎tests/PHPStan/Parallel/SchedulerTest.php‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,32 @@ public function testEveryFileIsScheduledExactlyOnce(): void
165165
}
166166
}
167167

168+
public function testAutoUsesAllUsableCores(): void
169+
{
170+
// 12 usable cores, plenty of jobs - auto follows the cores, not the old
171+
// fixed default of 8
172+
$scheduler = new Scheduler(1, Scheduler::AUTO, 1);
173+
$schedule = $scheduler->scheduleWork(12, array_fill(0, 200, 'file.php'), static fn (string $file): int => 0);
174+
175+
$this->assertSame(12, $schedule->getNumberOfProcesses());
176+
}
177+
178+
public function testAutoIsStillCappedByTheJobCount(): void
179+
{
180+
// 40 files -> 2 jobs at size 20, at least 2 jobs per process -> a single
181+
// worker no matter how many cores the machine has
182+
$scheduler = new Scheduler(20, Scheduler::AUTO, 2);
183+
$schedule = $scheduler->scheduleWork(32, array_fill(0, 40, 'file.php'), static fn (string $file): int => 0);
184+
185+
$this->assertSame(1, $schedule->getNumberOfProcesses());
186+
}
187+
188+
public function testAnExplicitLimitStillWins(): void
189+
{
190+
$scheduler = new Scheduler(1, 20, 1);
191+
$schedule = $scheduler->scheduleWork(32, array_fill(0, 200, 'file.php'), static fn (string $file): int => 0);
192+
193+
$this->assertSame(20, $schedule->getNumberOfProcesses());
194+
}
195+
168196
}

0 commit comments

Comments
 (0)