
# 並列ワーカー

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`などで使用）:

- ジョブが1つの場合（`-j 1`）、常にプロセス内で実行されます。
- デフォルトのテキストスキャンでは、プールが有効になるまでにおよそ`jobs * 2_000`個のファイルが必要です。
- 件数、一致するファイル、一致しないファイルの各モードでは、ファイルごとの処理が少ないため、しきい値はおよそ`jobs * 750`個に下がります。
- 短い識別子形式の固定文字列パターンでは、リテラルの高速処理が非常に低コストで、フォークが割に合わないことが多いため、しきい値はおよそ`jobs * 4_000`個に上がります。
- AST検索とAST書き換えでは、`jobs * 750`のしきい値を使います。

これらの数値は目安であり、保証値ではありません。WordPressのベンチマークコーパスを使って調整しており、見直すことがあります。

## プールの仕組み

`Greph\Parallel\WorkSplitter`はファイル一覧をN個のチャンクに分配します。`Greph\Parallel\WorkerPool`は各チャンクにつき1つのワーカープロセスをフォークします。各ワーカーは割り当てられた範囲で同じクロージャを実行し、`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`ワーカー）はプールを測定していません。しきい値が`jobs * 4_000`個のファイルである短いリテラル`function`を検索しているため、コーパスの規模を超えており、すべての行がプロセス内で実行されます（[#8](https://github.com/inline0/greph/issues/8)）。

## 制限事項

- 現在のプールはワークスティーリングに対応していません。あるチャンクの処理がほかより大幅に重い場合、プールは最も遅いワーカーを待ちます。
- ワーカーの結果は、すべてのワーカーが終了するまでバッファーされます。長時間のスキャン結果をストリーミング出力する機能（`rg --json`がイベントをストリーミングする方式）は、並列モードではサポートされません。
- 親プロセスは結果を統合した後に並べ替えるため、単一プロセスのスキャンと比べてもワーカープールでピークメモリ使用量は減りません。

これらの制限は、Grephが想定するワークロード（対話型コード検索、エージェントの処理ループ、スナップショットを対象としたCIジョブ）では許容できます。実データのコーパスでボトルネックになることがプロファイリングで示された場合は見直します。
