
# 并行工作进程

Greph 会在工作量足以抵消开销时，将文本和 AST 扫描分派到工作进程池。该进程池基于 `pcntl_fork()` 构建；如果 `ext-pcntl` 不可用（Windows、某些 PaaS 环境或受限托管环境），则回退到进程内执行。

## 启用并行扫描

在 CLI 上设置 `-j N`，或在相关选项对象中设置 `jobs: N`：

```bash
./vendor/bin/greph -F -j 8 "function" src
./vendor/bin/greph -p '$obj->$method($$$ARGS)' -j 4 src
./vendor/bin/rg --threads 8 -F "function" src
./vendor/bin/sg run --threads 8 --pattern '$obj->$method($$$ARGS)' src
```

```php
use Greph\Greph;
use Greph\Text\TextSearchOptions;

Greph::searchText(
    'function',
    'src',
    new TextSearchOptions(fixedString: true, jobs: 8),
);
```

## 并行何时启动

Greph 并非总会并行运行。启动 N 个进程并通过管道往返传递结果需要开销；对于较短的文件列表，这些开销会超过搜索本身的成本。因此，门面会根据作业数量、搜索模式和模式形状计算阈值。只有文件列表超过阈值时，才会分派到工作进程池。

当前启发式规则（位于 `Greph\Greph::shouldUseTextWorkers` 及相关组件中）：

- 单作业运行（`-j 1`）始终在进程内执行。
- 默认文本扫描需要大约 `jobs * 2_000` 个文件才会启用进程池。
- 计数、包含匹配项的文件和不包含匹配项的文件模式会将阈值降低到大约 `jobs * 750`，因为每个文件的工作量较少。
- 对于较短、类似标识符的固定字符串模式，阈值会提高到大约 `jobs * 4_000`，因为字面量快速路径已经非常快，启动进程很少划算。
- AST 搜索和 AST 重写使用 `jobs * 750` 阈值。

这些数字是启发式设定，并非保证值。它们根据 WordPress 基准测试语料调优，之后可能会重新调整。

## 进程池的工作方式

`Greph\Parallel\WorkSplitter` 会将文件列表分成 N 个块。`Greph\Parallel\WorkerPool` 会为每个块派生一个工作进程；每个工作进程对其文件切片运行相同的闭包，`Greph\Parallel\ResultCollector` 则通过每个工作进程对应的 Unix 套接字对读取序列化结果。父进程会等待所有工作进程完成，然后合并并重新排序结果集，确保无论工作如何拆分，输出顺序都保持稳定。

文本模式使用自定义的线路编码器（`Greph\Text\TextResultCodec`），以缩小序列化载荷。AST 模式使用 PHP 的标准 `serialize()`。

## pcntl 不可用时

如果 `pcntl_fork` 不可调用（Windows、经过强化的 PHP 构建），进程池会透明地回退为在进程内运行每个块。结果与 `-j 1` 相同。Greph 不会报错，只会以单线程运行。

你可以在运行时通过检查 `function_exists('pcntl_fork')` 来检测这一点。

## 调优建议

- 将 `-j` 设为物理核心数的一半，然后逐渐提高，直到性能不再提升。
- 对小型代码库进行纯字面量固定字符串搜索时，单进程通常比任何并行配置都快。
- 没有已发布的基准测试评估并行 AST 搜索或重写。在确定作业数前，请在自己的目录树上进行测量。
- README 中标为“扫描模式：并行文本”的行（在 WordPress 语料上使用 `1`、`2` 和 `4` 个工作进程）并未测量进程池的性能：它们搜索的是较短的字面量 `function`，其 `jobs * 4_000` 个文件的阈值高于语料规模，因此每一行实际上都在进程内运行（[#8](https://github.com/inline0/greph/issues/8)）。

## 限制

- 当前进程池不支持工作窃取。如果一个块的负载远重于其他块，进程池会等待最慢的工作进程。
- 工作进程会将结果缓存起来，直到所有进程完成。并行模式不支持长时间扫描时的流式输出（如 `rg --json` 流式传输事件的方式）。
- 父进程会在合并结果后重新排序，因此与单进程扫描相比，工作进程池不会降低峰值内存用量。

对于 Greph 的目标工作负载（交互式代码搜索、代理循环，以及针对快照运行的 CI 作业）而言，这些限制可以接受。如果性能分析显示它们在实际语料中形成瓶颈，我们会重新评估。
