Перейти к содержимому

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

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
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).

Ограничения

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

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