networking - Advertencias de select/poll vs. reactores epoll en Twisted
scalability capacity-planning (2)
En las pruebas en mi empresa, surgió un problema con epoll (), por lo tanto, un costo único en comparación con select.
Al intentar leer desde la red con un tiempo de espera excedido, crear un epoll_fd (en lugar de un FD_SET) y agregar el fd al epoll_fd es mucho más costoso que crear un FD_SET (que es un malloc simple).
Según la respuesta anterior, a medida que aumenta el número de FD en el proceso, el costo de select () aumenta, pero en nuestras pruebas, incluso con los valores de fd en los 10,000, seleccionar aún era un ganador. Estos son casos en los que solo hay un fd que un subproceso está esperando, y simplemente intentan superar el hecho de que la lectura de red y la escritura de red no exceden el tiempo de espera cuando se utiliza un modelo de cadena de bloqueo. Por supuesto, los modelos de hilos de bloqueo son de bajo rendimiento en comparación con los sistemas de reactores sin bloqueo, pero hay ocasiones en las que, para integrarse con una base de códigos heredados en particular, se requiere.
Este tipo de caso de uso es raro en las aplicaciones de alto rendimiento, porque un modelo de reactor no necesita crear un nuevo epoll_fd cada vez. Para el modelo donde un epoll_fd es de larga vida --- que es claramente preferido para cualquier diseño de servidor de alto rendimiento --- epoll es el claro ganador en todos los sentidos.
Todo lo que he leído y experimentado (aplicaciones basadas en Tornado) me lleva a pensar que ePoll es un reemplazo natural para las redes basadas en Select y Poll, especialmente con Twisted. Lo que me vuelve paranoico, es bastante raro que una mejor técnica o metodología no tenga un precio.
La lectura de un par de docenas de comparaciones entre epoll y alternativas muestra que epoll es claramente el campeón de velocidad y escalabilidad, específicamente que escala de manera lineal, lo cual es fantástico. Dicho esto, ¿qué pasa con el procesador y la utilización de la memoria, todavía es epoll el campeón?
Para cantidades muy pequeñas de sockets (varía dependiendo de su hardware, por supuesto, pero estamos hablando de algo del orden de 10 o menos), select puede vencer a epoll en el uso de la memoria y la velocidad del tiempo de ejecución. Por supuesto, para un número tan pequeño de enchufes, ambos mecanismos son tan rápidos que realmente no te importa esta diferencia en la gran mayoría de los casos.
Una aclaración, sin embargo. Ambas escalas select y epoll linealmente. Sin embargo, una gran diferencia es que las API orientadas al espacio de usuario tienen complejidades que se basan en cosas diferentes. El costo de una llamada de select
va más o menos al valor del descriptor de archivo con el número más alto que lo pasa. Si seleccionas en una sola fd, 100, eso es aproximadamente el doble de caro que seleccionar en una sola fd, 50. Agregar más fds por debajo de la más alta no es muy libre, por lo que es un poco más complicado que esto en la práctica, pero esto es una buena primera aproximación para la mayoría de las implementaciones.
El costo de epoll es más cercano al número de descriptores de archivo que realmente tienen eventos en ellos. Si está monitoreando 200 descriptores de archivos, pero solo 100 de ellos tienen eventos en ellos, entonces (más o menos) solo paga por esos 100 descriptores de archivos activos. Aquí es donde epoll tiende a ofrecer una de sus mayores ventajas sobre select. Si tiene miles de clientes que están inactivos en su mayoría, cuando usa seleccionar todavía está pagando por los mil. Sin embargo, con epoll, es como si solo tuviera unas pocas: solo está pagando por las que están activas en un momento dado.
Todo esto significa que epoll dará lugar a un menor uso de la CPU para la mayoría de las cargas de trabajo. En lo que respecta al uso de la memoria, es un poco complicado. select
logra representar toda la información necesaria de una manera muy compacta (un bit por descriptor de archivo). Y la limitación FD_SETSIZE (típicamente 1024) sobre cuántos descriptores de archivos puede usar con select
significa que nunca gastará más de 128 bytes para cada uno de los tres conjuntos de fd que puede usar con select
(lectura, escritura, excepción). Comparado con esos 384 bytes máximos, epoll es una especie de cerdo. Cada descriptor de archivo está representado por una estructura de múltiples bytes. Sin embargo, en términos absolutos, todavía no va a usar mucha memoria. Puede representar una gran cantidad de descriptores de archivos en unas pocas docenas de kilobytes (aproximadamente 20k por 1000 descriptores de archivos, creo). Y también puede considerar el hecho de que tiene que gastar todos los 384 de esos bytes con select
si solo desea monitorear un descriptor de archivo pero su valor es 1024, mientras que con epoll solo gastaría 20 bytes. Aún así, todos estos números son bastante pequeños, por lo que no hace mucha diferencia.
Y también está ese otro beneficio de epoll, que tal vez ya conozcas, que no está limitado a los descriptores de archivos FD_SETSIZE. Puedes usarlo para monitorear tantos descriptores de archivos como tengas. Y si solo tiene un descriptor de archivo, pero su valor es mayor que FD_SETSIZE, epoll también funciona con eso, pero select
no.
Aleatoriamente, también recientemente descubrí un pequeño inconveniente para epoll
en comparación con select
o poll
. Si bien ninguna de estas tres API admite archivos normales (es decir, archivos en un sistema de archivos), select
y poll
esta falta de soporte como informe de descripciones tales como siempre legibles y siempre escribibles. Esto los hace inadecuados para cualquier clase significativa de E / S de sistema de archivos sin bloqueo, un programa que usa select
o poll
y encuentra un descriptor de archivo del sistema de archivos que al menos continuará funcionando (o si falla, no lo hará). sea por select
o poll
), aunque tal vez no con el mejor rendimiento.
Por otro lado, epoll
fallará rápidamente con un error ( EPERM
, aparentemente) cuando se le epoll
que controle dicho descriptor de archivo. Estrictamente hablando, esto es apenas incorrecto. Simplemente está señalando su falta de apoyo de una manera explícita. Normalmente, aplaudo las condiciones de falla explícitas, pero esta no está documentada (hasta donde puedo decir) y resulta en una aplicación completamente rota, en lugar de una que simplemente opera con un rendimiento potencialmente degradado.
En la práctica, el único lugar donde he visto esto es cuando se interactúa con stdio. Un usuario puede redirigir stdin o stdout desde / hacia un archivo normal. Mientras que previamente stdin y stdout habrían sido un conducto (soportado por epoll), entonces se convierte en un archivo normal y epoll falla ruidosamente, rompiendo la aplicación.