PHP RFC, що визначає нативний Polling API, було схвалено та об'єднано з основною гілкою мови, що надає розробникам швидкий спосіб опитування дескрипторів файлів на рівні ОС. У поєднанні з нещодавно доданими Fibers, PHP тепер має вбудовану ефективну основу для асинхронного коду — прогалину, яка довгий час змушувала бібліотеки створювати власні обхідні шляхи.

Fibers та event loop: розділення

Fibers — це примітив керування потоком виконання низького рівня. Вони дозволяють функції призупинити своє виконання в обраній точці, а згодом відновити його саме там, де воно зупинилося. Що важливо, fiber нічого не знає про сокети, таймери чи будь-які інші джерела I/O; він просто ставиться на паузу та перезапускається за запитом.

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; він просто надає event loop швидкість і масштабованість, яких той потребує.

Чому ці зміни важливі для ReactPHP, Amp та інших

ReactPHP та Amp v3 побудували власні рівні абстракції поверх механізмів опитування ОС. Ці рівні містять кілька шляхів виконання коду, кожен з яких налаштований під конкретну платформу, і їх необхідно синхронізувати зі змінами в ядрі. З нативним Polling API бібліотеки зможуть відмовитися від більшої частини цієї інфраструктури та покластися на єдиний виклик, що надається ядром мови.

  • Підтримка – Менше гілок під конкретні платформи означає менше багів і меншу площу для перевірок безпеки.
  • Продуктивність – Нативний виклик взаємодіє безпосередньо з epoll/kqueue.
  • Портативність – Код, що працює на «чистому» PHP, тепер отримує однакову базову продуктивність на всіх підтримуваних операційних системах без потреби в додаткових розширеннях.

Swoole, на противагу, залишається повноцінною заміною середовища виконання (runtime), яка постачається з власним event loop та системою корутин. Polling API не впливає на компроміси Swoole; розробники, яким потрібна наднизька затримка або кастомне керування пам'яттю, все одно розглядатимуть його як окремий варіант.

Швидкий бенчмарк показує результат

Мінімальний планувальник отримував кілька URL-адрес через сирі сокети, використовуючи єдиний Io\Poll\Context для керування кількома fibers. Було зроблено два спостереження:

  1. Швидкість – Додавання більшої кількості одночасних запитів не збільшує загальний час виконання; пакет запитів завершується так само швидко, як і найповільніший окремий запит. Іншими словами, накладні витрати на паралелізм фактично дорівнюють нулю.
  2. Витрати CPU – При використанні stream_select() використання CPU помітно зростає зі збільшенням кількості потоків, оскільки функція повинна перебирати кожен дескриптор при кожному виклику. Витрати Polling API залишаються стабільними при переході від трьох потоків до тридцяти завдяки здатності ядра моніторити багато дескрипторів за один системний виклик.

Що розробникам робити зараз

  • Переходьте на Amp v3, якщо ви віддаєте перевагу стилю, орієнтованому на fibers, який безпосередньо відповідає новому API. Його публічний інтерфейс уже відображається на базовий poller, тому ви отримаєте перевагу без переписування коду.
  • Залишайтеся з ReactPHP, якщо вам подобається явний контроль над життєвим циклом loop.
  • Уникайте власних планувальників для робочих навантажень (production). Не пишіть власний планувальник для продакшну.