El RFC de PHP que define una Polling API nativa ha sido aprobado e integrado en la rama master del lenguaje, ofreciendo a los desarrolladores una forma rápida, a nivel de sistema operativo, de realizar el polling de descriptores de archivos. Junto con las recientemente añadidas Fibers, PHP cuenta ahora con una base integrada y eficiente para el código asíncrono, una carencia que durante mucho tiempo ha obligado a las librerías a improvisar sus propias soluciones alternativas.
Fibers y el event loop, separados
Las Fibers son una primitiva de flujo de control de bajo nivel. Permiten que una función suspenda su ejecución en un punto elegido y luego se reanude exactamente donde se quedó. Fundamentalmente, una fiber no sabe nada sobre sockets, temporizadores ni ninguna otra fuente de E/S; simplemente se pausa y se reinicia bajo demanda.
Un event loop es el scheduler que decide cuándo debe reanudarse una fiber pausada. En una pila asíncrona típica, el loop vigila un conjunto de descriptores de archivos, espera a que sean legibles o escribibles y luego despierta a la fiber correspondiente.
PHP ya contaba con Fibers, pero carecía de un mecanismo nativo para preguntar al sistema operativo qué descriptores estaban listos. El resultado era la dependencia de stream_select() o de extensiones externas, y cada librería asíncrona terminaba escribiendo sus propios back-ends para epoll en Linux, kqueue en BSD, IOCP en Windows, etc.
La nueva Polling API llena ese vacío. Expone un wrapper ligero sobre las facilidades de polling del sistema operativo (epoll en Linux, kqueue en BSD/macOS, etc.). La API no reemplaza a las Fibers; simplemente le otorga al event loop la velocidad y escalabilidad que necesita.
Por qué este cambio es importante para ReactPHP, Amp y sus aliados
ReactPHP y Amp v3 han construido sus propias capas de abstracción sobre los mecanismos de polling del sistema operativo. Esas capas contienen múltiples rutas de código, cada una ajustada para una plataforma específica, y deben mantenerse sincronizadas con los cambios del kernel. Con una Polling API nativa, las librerías pueden prescindir de la mayor parte de esa infraestructura y confiar en una única llamada proporcionada por el núcleo.
- Mantenimiento – Menos ramas específicas de plataforma significan menos errores y una menor superficie de exposición para revisiones de seguridad.
- Rendimiento – La llamada nativa se comunica directamente con epoll/kqueue.
- Portabilidad – El código que se ejecuta en PHP "vanilla" ahora obtiene el mismo rendimiento base en todos los sistemas operativos compatibles, sin necesidad de extensiones opcionales.
Swoole, por el contrario, sigue siendo un reemplazo completo del runtime que incluye su propio event loop y sistema de corrutinas. La Polling API no afecta a las ventajas y desventajas de Swoole; los desarrolladores que necesiten una latencia ultrabaja o una gestión de memoria personalizada seguirán considerándolo una opción aparte.
Un benchmark rápido lo cuenta todo
Un scheduler mínimo obtuvo varias URLs a través de sockets puros, utilizando un único Io\Poll\Context para gestionar múltiples fibers. Surgieron dos observaciones:
- Velocidad – Añadir más peticiones concurrentes no aumenta el tiempo total transcurrido; el lote de peticiones termina tan rápido como la petición individual más lenta. En otras palabras, la sobrecarga de concurrencia es efectivamente cero.
- Coste de CPU – Con
stream_select(), el uso de CPU aumenta notablemente a medida que crece el número de streams, porque la función debe iterar sobre cada descriptor en cada llamada. El coste de la Polling API se mantiene constante de tres a treinta streams, gracias a la capacidad del kernel para monitorizar muchos descriptores en una sola llamada al sistema.
Qué deben hacer los desarrolladores ahora
- Adoptar Amp v3 si prefieres un estilo nativo de fibers que se alinee directamente con la nueva API. Su interfaz pública ya se mapea al poller subyacente, por lo que obtienes el beneficio sin tener que reescribir tu código.
- Seguir con ReactPHP si prefieres un control explícito sobre el ciclo de vida del loop.
- Evitar schedulers personalizados para cargas de trabajo en producción. No escribas tu propio scheduler para producción.
