From 6d9c3da0c020705dac196aebf6809215d9a1a946 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 17 Mar 2026 09:50:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(compiler):=20=E6=B7=BB=E5=8A=A0=E5=B9=B6?= =?UTF-8?q?=E8=A1=8C=E7=BC=96=E8=AF=91=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=9A=E4=BB=BB=E5=8A=A1=E5=90=8C=E6=97=B6=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将原有逐个编译逻辑替换为统一的 compile 方法 - 新增 job 参数用于控制并行任务数量,默认值为 4 - 实现基于 pcntl_fork 的并行编译机制 - 添加并行模式下的错误处理和输出管理 - 在帮助信息中增加新的并行编译选项说明 - 优化编译过程中的进度提示和状态显示 --- bin/compiler.php | 9 +-- src/Php/CompilerBase.php | 1 + src/Php/Translator.php | 126 ++++++++++++++++++++++++++++++-- src/config/compiler_options.php | 7 ++ 4 files changed, 129 insertions(+), 14 deletions(-) diff --git a/bin/compiler.php b/bin/compiler.php index b4b4a2c2..2554a9d3 100755 --- a/bin/compiler.php +++ b/bin/compiler.php @@ -75,14 +75,7 @@ if ($translator->getBuildMode() == 'bin') { } // 编译所有 C++ 文件 -foreach ($sourceFiles as $cppFile) { - $objectFile = $translator->getObjectFile($cppFile); - $translator->compileFile($cppFile, $objectFile); - if (!is_file($objectFile)) { - throw new Exception("compile error"); - } - $objectFiles[] = $objectFile; -} +$objectFiles = $translator->compile($sourceFiles); // 连接所有目标文件,生成可执行文件 $translator->build($objectFiles); diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index c1b63495..faad75b1 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -132,6 +132,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected array $redoAfterDeclare = []; protected array $constData = []; protected int $optimizeLevel = 0; + protected int $maxJob = 4; protected string $buildMode = 'bin'; protected string $cxxflags = ''; protected string $ldflags = ''; diff --git a/src/Php/Translator.php b/src/Php/Translator.php index cb541876..a4e5da78 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -50,8 +50,9 @@ class Translator extends Preprocessor $this->climate->arguments->parse(); $this->optimizeLevel = $this->climate->arguments->get('optimize'); - $this->buildMode = $this->climate->arguments->get('mode'); - $this->debugLine = intval($this->climate->arguments->get('debug-line')); + $this->buildMode = $this->climate->arguments->get('mode'); + $this->debugLine = intval($this->climate->arguments->get('debug-line')); + $this->maxJob = intval($this->climate->arguments->get('job')); $this->debugInfo = $this->climate->arguments->defined('debug-info'); $this->noLiteralStrings = $this->climate->arguments->get('noLiteralStrings'); $this->enableProfiler = $this->climate->arguments->defined('profile'); @@ -84,6 +85,7 @@ class Translator extends Preprocessor $climate->tab()->out('-h, --help Show this help message'); $climate->tab()->out('-f, --force Force compile even if cache exists'); $climate->tab()->out('-m, --mode Compilation mode, -m bin(binary) or -m ext(extension), default: bin'); + $climate->tab()->out('-j, --job Number of parallel compilation jobs (default: 4)'); $climate->tab()->out('--no-literal-strings Disable literal strings optimization'); $climate->br(); @@ -262,22 +264,134 @@ class Translator extends Preprocessor return false; } - public function compileFile(string $cppFile, string $objectFile): void + public function compileFile(string $cppFile, string $objectFile, bool $parallel = false): void { if ($this->hasObjectFileCache($cppFile)) { - $this->climate->darkGray('skip: ' . $cppFile . ', cache exists'); + if (!$parallel) { + $this->climate->darkGray('skip: ' . $cppFile . ', cache exists'); + } return; } $cmd = $this->cppCompiler . ' -c ' . $cppFile . ' -o ' . $objectFile; $this->addCompilationOption($cmd, false); - $this->climate->comment($cmd); - passthru($cmd, $ret); + if (!$parallel) { + $this->climate->comment($cmd); + } + // 在并行模式下,抑制 passthru 的输出 + if ($parallel) { + exec($cmd . ' 2>&1', $output, $ret); + } else { + passthru($cmd, $ret); + } if ($ret !== 0) { + if ($parallel && !empty($output)) { + foreach ($output as $line) { + $this->climate->red($line); + } + } $this->error('compile failed: ' . $cppFile); } } + public function compile(array $sourceFiles): array + { + $objectFiles = []; + $job = $this->maxJob; + + // 如果只有一个文件或 job 为 1,则串行编译 + if (count($sourceFiles) <= 1 || $job <= 1) { + foreach ($sourceFiles as $cppFile) { + $objectFile = $this->getObjectFile($cppFile); + $this->compileFile($cppFile, $objectFile); + if (!is_file($objectFile)) { + throw new \Exception("compile error"); + } + $objectFiles[] = $objectFile; + } + return $objectFiles; + } + + // 并行编译 + $totalFiles = count($sourceFiles); + $runningProcesses = 0; + $processPipes = []; + $fileQueue = $sourceFiles; + $compiledCount = 0; + $failedFiles = []; + + $this->climate->blue("Starting parallel compilation with {$job} jobs for {$totalFiles} files"); + + while ($compiledCount < $totalFiles) { + // 启动新进程,直到达到最大并发数 + while ($runningProcesses < $job && !empty($fileQueue)) { + $cppFile = array_shift($fileQueue); + $objectFile = $this->getObjectFile($cppFile); + + $pid = pcntl_fork(); + if ($pid == -1) { + throw new \Exception("Failed to fork process"); + } elseif ($pid === 0) { + // 子进程:执行编译 + try { + $this->compileFile($cppFile, $objectFile, true); + if (!is_file($objectFile)) { + exit(1); + } + exit(0); + } catch (\Throwable $e) { + // 在子进程中不抛出异常,直接退出 + exit(1); + } + } else { + // 父进程:记录子进程 + $processPipes[$pid] = ['file' => $cppFile, 'object' => $objectFile]; + $runningProcesses++; + } + } + + // 等待任意一个子进程完成 + if ($runningProcesses > 0) { + $pid = pcntl_wait($status); + if ($pid > 0) { + $processInfo = $processPipes[$pid] ?? null; + unset($processPipes[$pid]); + $runningProcesses--; + + $exitCode = pcntl_wexitstatus($status); + if ($exitCode !== 0) { + $failedFile = $processInfo['file'] ?? 'unknown'; + $this->climate->red("Compilation failed: {$failedFile}"); + $failedFiles[] = $failedFile; + } else { + if ($processInfo) { + $objectFiles[] = $processInfo['object']; + } + } + $compiledCount++; + } + } + } + + // 确保所有子进程都已结束 + while ($runningProcesses > 0) { + $pid = pcntl_wait($status); + if ($pid > 0) { + unset($processPipes[$pid]); + $runningProcesses--; + $compiledCount++; + } + } + + if (!empty($failedFiles)) { + throw new \Exception("Compilation failed for: " . implode(', ', $failedFiles)); + } + + $this->climate->green("Successfully compiled {$totalFiles} files"); + + return $objectFiles; + } + public function build(array $objectFiles): void { $objectList = implode(' ', $objectFiles); diff --git a/src/config/compiler_options.php b/src/config/compiler_options.php index 2518d1fa..60183ad5 100644 --- a/src/config/compiler_options.php +++ b/src/config/compiler_options.php @@ -56,4 +56,11 @@ return [ 'description' => 'Enable debug info', 'required' => false, ], + 'job' => [ + 'prefix' => 'j', + 'longPrefix' => 'job', + 'description' => 'Number of jobs to run in parallel', + 'required' => false, + 'defaultValue' => 4, + ], ]; \ No newline at end of file