PHP RFC, определяющий нативный Polling API, был одобрен и объединен с основной веткой (master branch) языка, что дает разработчикам быстрый способ опроса файловых дескрипторов на уровне ОС. В сочетании с недавно добавленными Fibers, PHP теперь обладает встроенным эффективным фундаментом для асинхронного кода — пробел, который долгое время вынуждал библиотеки изобретать собственные обходные пути.

Fibers и цикл событий: разделение понятий

Fibers — это низкоуровневый примитив управления потоком выполнения. Они позволяют функции приостановить выполнение в выбранной точке и позже возобновить его именно там, где оно остановилось. Что крайне важно, fiber ничего не знает о сокетах, таймерах или любых других источниках ввода-вывода; он просто ставится на паузу и перезапускается по требованию.

Event loop (цикл событий) — это планировщик, который решает, когда нужно возобновить приостановленный fiber. В типичном асинхронном стеке цикл отслеживает набор файловых дескрипторов, ждет, когда они станут доступными для чтения или записи, а затем пробуждает соответствующий fiber.

Ранее в PHP были Fibers, но отсутствовал нативный механизм для запроса у операционной системы информации о том, какие дескрипторы готовы к работе. В результате приходилось полагаться на stream_select() или внешние расширения, и каждой асинхронной библиотеке приходилось писать собственные бэкенды для Linux’s epoll, BSD’s kqueue, Windows’ IOCP и так далее.

Новый Polling API восполняет этот пробел. Он представляет собой тонкую обертку над механизмами опроса ОС (epoll в Linux, kqueue в BSD/macOS и т. д.). Этот API не заменяет Fibers; он просто дает циклу событий необходимую скорость и масштабируемость.

Почему это важно для ReactPHP, Amp и других

ReactPHP и Amp v3 построили собственные уровни абстракции поверх механизмов опроса ОС. Эти уровни содержат множество путей выполнения, каждый из которых оптимизирован под конкретную платформу, и их необходимо синхронизировать с изменениями в ядре. С нативным Polling API библиотеки смогут избавиться от большей части этой технической обвязки и полагаться на единый вызов, предоставляемый ядром языка.

  • Поддержка — меньше ветвлений под конкретные платформы означает меньше багов и меньшую область для аудита безопасности.
  • Производительность — нативный вызов напрямую взаимодействует с epoll/kqueue.
  • Переносимость — код, работающий на «чистом» (vanilla) PHP, теперь получает одинаковую базовую производительность во всех поддерживаемых операционных системах без необходимости использования дополнительных расширений.

Swoole, напротив, остается полноценной заменой среды выполнения (runtime), поставляя собственный цикл событий и систему корутин. Polling API не влияет на компромиссы Swoole; разработчики, которым требуется сверхнизкая задержка или кастомное управление памятью, по-прежнему будут рассматривать его как отдельный вариант.

Небольшой бенчмарк говорит сам за себя

Минималистичный планировщик запрашивал несколько URL через «сырые» сокеты, используя единственный Io\Poll\Context для управления несколькими fibers. Были сделаны два наблюдения:

  1. Скорость — увеличение количества параллельных запросов не увеличивает общее время выполнения; пакет запросов завершается так же быстро, как и самый медленный одиночный запрос. Иными словами, накладные расходы на конкурентность фактически равны нулю.
  2. Затраты CPU — при использовании stream_select() потребление CPU заметно растет по мере увеличения количества потоков, так как функция должна перебирать каждый дескриптор при каждом вызове. Затраты Polling API остаются стабильными при переходе от трех потоков к тридцати благодаря способности ядра отслеживать множество дескрипторов за один системный вызов.

Что делать разработчикам сейчас

  • Переходите на Amp v3, если предпочитаете стиль, ориентированный на fibers, который напрямую соответствует новому API. Его публичный интерфейс уже сопоставлен с базовым поллером, так что вы получите преимущества без переписывания кода.
  • Оставайтесь на ReactPHP, если вам нравится явный контроль над жизненным циклом цикла событий.
  • Избегайте создания собственных планировщиков для рабочих нагрузок в продакшене. Не пишите свой собственный планировщик для продакшена.