
# Równoległe procesy robocze

Greph rozdziela skanowanie tekstu i AST między procesy robocze, gdy ilość pracy uzasadnia związane z tym koszty. Pula korzysta z `pcntl_fork()` i przechodzi na wykonywanie w bieżącym procesie, gdy `ext-pcntl` nie jest dostępne (Windows, niektóre środowiska PaaS, hosting z ograniczeniami).

## Włączanie skanowania równoległego

Ustaw `-j N` w CLI albo `jobs: N` w odpowiednim obiekcie opcji:

```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),
);
```

## Kiedy uruchamia się tryb równoległy

Greph nie działa równolegle bezwarunkowo. Uruchamianie N procesów i przesyłanie wyników w obie strony przez potoki wiąże się z narzutem, który przy małych listach plików przewyższa koszt samego wyszukiwania. Zamiast tego fasada oblicza próg na podstawie liczby zadań, trybu wyszukiwania i kształtu wzorca. Do puli trafiają tylko listy plików przekraczające ten próg.

Obecne heurystyki (w `Greph\Greph::shouldUseTextWorkers` i powiązanych elementach):

- Uruchomienia z jednym zadaniem (`-j 1`) zawsze odbywają się w bieżącym procesie.
- Domyślne skanowanie tekstu wymaga około `jobs * 2_000` plików, aby uruchomić pulę.
- Tryby zliczania, plików z dopasowaniami i plików bez dopasowań obniżają próg do około `jobs * 750`, ponieważ praca na plik jest tańsza.
- Krótkie wzorce dokładnych ciągów przypominające identyfikatory podnoszą próg do około `jobs * 4_000`, ponieważ szybka ścieżka dopasowań dosłownych jest już tak tania, że uruchamianie procesów rzadko się opłaca.
- Wyszukiwanie i przepisywanie AST korzystają z progu `jobs * 750`.

To wartości heurystyczne, a nie gwarancje. Są dostrojone do korpusu testów wydajnościowych WordPress i mogą zostać zmienione.

## Jak działa pula

`Greph\Parallel\WorkSplitter` dzieli listę plików na N porcji. `Greph\Parallel\WorkerPool` uruchamia oddzielny proces roboczy dla każdej porcji; każdy proces wykonuje tę samą funkcję na przypisanym mu fragmencie, a `Greph\Parallel\ResultCollector` odczytuje zaserializowane wyniki przez osobną parę gniazd Unix dla każdego procesu. Proces nadrzędny czeka na zakończenie wszystkich procesów, a następnie scala i ponownie sortuje wyniki, dzięki czemu ich kolejność jest stabilna niezależnie od podziału pracy.

W trybie tekstowym pula procesów roboczych korzysta z niestandardowego kodera przewodowego (`Greph\Text\TextResultCodec`), aby zmniejszyć rozmiar serializowanych danych. Tryb AST używa standardowego `serialize()` należącego do PHP.

## Gdy pcntl jest niedostępne

Jeśli `pcntl_fork` nie jest dostępne do wywołania (Windows, kompilacje PHP z zaostrzonymi zabezpieczeniami), pula bezproblemowo przechodzi na wykonywanie każdej porcji w bieżącym procesie. Wynik jest taki sam jak w `-j 1`. Greph nie zgłasza błędu, tylko działa jednowątkowo.

Możesz wykryć taką sytuację w czasie wykonywania, sprawdzając `function_exists('pcntl_fork')`.

## Wskazówki dotyczące dostrajania

- Zacznij od ustawienia `-j` na połowę liczby fizycznych rdzeni i zwiększaj wartość, aż przestaniesz zauważać poprawę.
- W małych repozytoriach wyszukiwanie oparte wyłącznie na dokładnych ciągach zwykle działa szybciej w jednym procesie niż przy dowolnej konfiguracji równoległej.
- Żaden opublikowany test wydajnościowy nie mierzy równoległego wyszukiwania ani przepisywania AST. Zmierz wydajność na własnym drzewie przed ustaleniem liczby zadań.
- Wiersze „Tryb skanowania: tekst równoległy” w README (`1`, `2` i `4` procesy robocze w korpusie WordPress) nie mierzą działania puli: wyszukują krótki dosłowny ciąg `function`, którego próg wynoszący `jobs * 4_000` plików przekracza rozmiar korpusu, więc każdy wiersz działa w bieżącym procesie ([#8](https://github.com/inline0/greph/issues/8)).

## Ograniczenia

- Obecna pula nie obsługuje równoważenia obciążenia przez przekazywanie zadań. Jeśli jedna porcja jest znacznie cięższa od pozostałych, pula czeka na najwolniejszy proces.
- Wyniki procesów roboczych są buforowane do czasu zakończenia wszystkich. Tryb równoległy nie obsługuje strumieniowego wyjścia z długotrwałego skanowania, tak jak robi to `rg --json`, emitując zdarzenia.
- Proces nadrzędny ponownie sortuje wyniki po scaleniu, więc pula procesów roboczych nie zmniejsza maksymalnego zużycia pamięci w porównaniu ze skanowaniem w jednym procesie.

Te ograniczenia są akceptowalne dla obciążeń, na które ukierunkowano Greph: interaktywne wyszukiwanie kodu, pętle agentów i zadania CI na migawkach. Zostaną ponownie ocenione, jeśli profilowanie wykaże, że stanowią wąskie gardło w rzeczywistych korpusach.
