
# Параллельные рабочие процессы

Greph распределяет текстовое сканирование и сканирование AST между рабочими процессами, если объем работы оправдывает накладные расходы. Пул построен на `pcntl_fork()` и переходит к выполнению в одном процессе, если `ext-pcntl` недоступен (Windows, некоторые среды PaaS, хостинг с ограничениями).

## Включение параллельного сканирования

Укажите `-j N` для CLI или `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 используется порог `jobs * 750`.

Это эвристические значения, а не гарантии. Они настроены на наборе данных для бенчмарков WordPress и могут быть пересмотрены.

## Как работает пул

`Greph\Parallel\WorkSplitter` распределяет список файлов по N частям. `Greph\Parallel\WorkerPool` запускает отдельный рабочий процесс для каждой части; каждый процесс выполняет ту же функцию над назначенной ему частью, а `Greph\Parallel\ResultCollector` считывает сериализованные результаты через отдельную пару сокетов Unix для каждого процесса. Родительский процесс дожидается завершения всех рабочих процессов, затем объединяет результаты и сортирует их заново. Поэтому порядок вывода остается стабильным независимо от распределения работы.

В текстовом режиме пул рабочих процессов использует собственный сетевой кодек (`Greph\Text\TextResultCodec`), чтобы уменьшить размер сериализованных данных. В режиме AST применяется стандартный `serialize()` из PHP.

## Если pcntl недоступен

Если вызвать `pcntl_fork` невозможно (Windows, сборки PHP с усиленной защитой), пул автоматически переходит к выполнению каждой части в одном процессе. Результат такой же, как у `-j 1`. Greph не выдает ошибку, а просто выполняется в одном потоке.

Проверить это во время выполнения можно с помощью `function_exists('pcntl_fork')`.

## Рекомендации по настройке

- Начните с `-j`, равного половине числа физических ядер, и увеличивайте значение, пока прирост производительности не прекратится.
- Для поиска только по буквальным фиксированным строкам в небольших репозиториях один процесс обычно быстрее любой параллельной конфигурации.
- Опубликованные бенчмарки не измеряют параллельный поиск или перезапись AST. Прежде чем выбирать число задач, проведите измерения на своем дереве файлов.
- Строки «Режим сканирования: параллельный текстовый поиск» в README (с `1`, `2` и `4` рабочими процессами на наборе данных WordPress) не измеряют работу пула: в них выполняется поиск короткой буквальной строки `function`, для которой порог в `jobs * 4_000` файлов превышает размер набора данных. Поэтому каждая строка выполняется в одном процессе ([#8](https://github.com/inline0/greph/issues/8)).

## Ограничения

- Текущий пул не поддерживает динамическое перераспределение задач. Если одна часть значительно тяжелее остальных, пул ждет завершения самого медленного рабочего процесса.
- Результаты рабочих процессов буферизуются до завершения всех процессов. Потоковый вывод длительного сканирования (когда события передаются по мере поступления, как в `rg --json`) в параллельном режиме не поддерживается.
- Родительский процесс повторно сортирует результаты после объединения, поэтому пул рабочих процессов не снижает пиковое потребление памяти по сравнению со сканированием в одном процессе.

Эти ограничения приемлемы для задач, на которые рассчитан Greph (интерактивный поиск по коду, циклы работы агентов, задания CI на снимках). К ним вернутся, если профилирование покажет, что они становятся узким местом на реальных корпусах.
