Параллельные рабочие процессы
Greph распределяет текстовое сканирование и сканирование AST между рабочими процессами, если объем работы оправдывает накладные расходы. Пул построен на pcntl_fork() и переходит к выполнению в одном процессе, если ext-pcntl недоступен (Windows, некоторые среды PaaS, хостинг с ограничениями).
Включение параллельного сканирования
Укажите -j N для CLI или jobs: N в соответствующем объекте параметров:
./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
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).
Ограничения
- Текущий пул не поддерживает динамическое перераспределение задач. Если одна часть значительно тяжелее остальных, пул ждет завершения самого медленного рабочего процесса.
- Результаты рабочих процессов буферизуются до завершения всех процессов. Потоковый вывод длительного сканирования (когда события передаются по мере поступления, как в
rg --json) в параллельном режиме не поддерживается. - Родительский процесс повторно сортирует результаты после объединения, поэтому пул рабочих процессов не снижает пиковое потребление памяти по сравнению со сканированием в одном процессе.
Эти ограничения приемлемы для задач, на которые рассчитан Greph (интерактивный поиск по коду, циклы работы агентов, задания CI на снимках). К ним вернутся, если профилирование покажет, что они становятся узким местом на реальных корпусах.