From 06ac35aa79bcbcdad562a7ce1675bb27e372291f Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 28 Apr 2026 15:43:58 +0200 Subject: [PATCH 1/3] Runner: distribute files round-robin by size in parallel mode --- src/Runner.php | 52 +++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/Runner.php b/src/Runner.php index 72e5e06f38..a4a92178bf 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -20,6 +20,7 @@ use PHP_CodeSniffer\Files\DummyFile; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Files\FileList; +use PHP_CodeSniffer\Files\LocalFile; use PHP_CodeSniffer\Util\Cache; use PHP_CodeSniffer\Util\Common; use PHP_CodeSniffer\Util\ExitCode; @@ -397,18 +398,37 @@ private function run() } } else { // Batching and forking. - $childProcs = []; - $numPerBatch = ceil($numFiles / $this->config->parallel); + $childProcs = []; + + // Distribute files round-robin in size order so each batch + // ends up with a similar total workload. + $sortedPaths = []; + $todo->rewind(); + while ($todo->valid() === true) { + $sortedPaths[] = $todo->key(); + $todo->next(); + } + + $sizes = []; + foreach ($sortedPaths as $path) { + $size = @filesize($path); + if ($size === false) { + $size = 0; + } - for ($batch = 0; $batch < $this->config->parallel; $batch++) { - $startAt = ($batch * $numPerBatch); - if ($startAt >= $numFiles) { - break; + $sizes[$path] = $size; + } + + usort( + $sortedPaths, + static function ($a, $b) use ($sizes) { + return ($sizes[$b] - $sizes[$a]); } + ); - $endAt = ($startAt + $numPerBatch); - if ($endAt > $numFiles) { - $endAt = $numFiles; + for ($batch = 0; $batch < $this->config->parallel; $batch++) { + if ($batch >= $numFiles) { + break; } $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child'); @@ -418,12 +438,6 @@ private function run() } elseif ($pid !== 0) { $childProcs[$pid] = $childOutFilename; } else { - // Move forward to the start of the batch. - $todo->rewind(); - for ($i = 0; $i < $startAt; $i++) { - $todo->next(); - } - // Reset the reporter to make sure only figures from this // file batch are recorded. $this->reporter->totalFiles = 0; @@ -437,12 +451,11 @@ private function run() // Process the files. $pathsProcessed = []; ob_start(); - for ($i = $startAt; $i < $endAt; $i++) { - $path = $todo->key(); - $file = $todo->current(); + for ($i = $batch; $i < $numFiles; $i += $this->config->parallel) { + $path = $sortedPaths[$i]; + $file = new LocalFile($path, $this->ruleset, $this->config); if ($file->ignored === true) { - $todo->next(); continue; } @@ -458,7 +471,6 @@ private function run() $this->processFile($file); $pathsProcessed[] = $path; - $todo->next(); } $debugOutput = ob_get_contents(); From 687fea77ca569f58b702da648b503cb84d4364cb Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 28 Apr 2026 15:54:56 +0200 Subject: [PATCH 2/3] Runner: disable parallel for STDIN --- src/Runner.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Runner.php b/src/Runner.php index a4a92178bf..448aa40abb 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -116,8 +116,11 @@ public function runPHPCS() // Disable caching if we are processing STDIN as we can't be 100% // sure where the file came from or if it will change in the future. + // Also disable parallel processing because STDIN is a single file + // held in memory and the workers can't see it across the fork. if ($this->config->stdin === true) { - $this->config->cache = false; + $this->config->cache = false; + $this->config->parallel = 1; } $this->run(); From a7f9f7f9ab0df9b0ab1cf677330f807e27f4b61b Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 28 Apr 2026 16:07:50 +0200 Subject: [PATCH 3/3] ci: re-run