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

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 зі знімками). До них повернуться, якщо профілювання покаже, що на реальних корпусах вони створюють вузьке місце.
