Advertencias de reactores select / poll frente a epoll en Twisted

95

Todo lo que he leído y experimentado (aplicaciones basadas en Tornado) me lleva a creer 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.

Leer un par de docenas de comparaciones entre epoll y alternativas muestra que epoll es claramente el campeón en velocidad y escalabilidad, específicamente que escala de manera lineal, lo cual es fantástico. Dicho esto, ¿qué pasa con la utilización del procesador y la memoria? ¿Epoll sigue siendo el campeón?

David
fuente

Respuestas:

190

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 uso de memoria y velocidad de ejecución. Por supuesto, para un número tan pequeño de sockets, ambos mecanismos son tan rápidos que realmente no le importa esta diferencia en la gran mayoría de los casos.

Sin embargo, una aclaración. Ambos seleccionan y escalan 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 selectllamada va aproximadamente con el valor del descriptor de archivo con el número más alto que le pasa. Si selecciona en un solo fd, 100, entonces eso es aproximadamente el doble de caro que seleccionar en un solo fd, 50. Agregar más fds por debajo del más alto no es del todo gratis, 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 está más cerca de la cantidad de descriptores de archivo que realmente tienen eventos en ellos. Si está monitoreando 200 descriptores de archivos, pero solo 100 de ellos tienen eventos, entonces (muy aproximadamente) solo está pagando por esos 100 descriptores de archivos activos. Aquí es donde epoll tiende a ofrecer una de sus principales ventajas sobre select. Si tiene mil clientes que en su mayoría están inactivos, entonces cuando usa Select todavía está pagando por los mil. Sin embargo, con epoll, es como si solo tuviera unos pocos: solo paga por los que están activos en un momento dado.

Todo esto significa que epoll conducirá a un menor uso de CPU para la mayoría de las cargas de trabajo. En lo que respecta al uso de la memoria, es un poco complicado. selectlogra 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 la cantidad de descriptores de archivo que puede usar selectsignifica que nunca gastará más de 128 bytes para cada uno de los tres conjuntos fd que puede usar conselect(lectura, escritura, excepción). Comparado con esos 384 bytes como máximo, epoll es una especie de cerdo. Cada descriptor de archivo está representado por una estructura de varios bytes. Sin embargo, en términos absolutos, todavía no utilizará mucha memoria. Puede representar una gran cantidad de descriptores de archivo en unas pocas docenas de kilobytes (aproximadamente 20k por cada 1000 descriptores de archivo, creo). Y también puede agregar el hecho de que tiene que gastar los 384 de esos bytes selectsi 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 existe otro beneficio de epoll, que quizás ya conozca, que no se limita a los descriptores de archivo FD_SETSIZE. Puede usarlo para monitorear tantos descriptores de archivos como tenga. Y si solo tiene un descriptor de archivo, pero su valor es mayor que FD_SETSIZE, epoll también trabaja con eso, pero selectno lo hace.

Al azar, también he descubierto recientemente un pequeño inconveniente en epollcomparación con selecto poll. Si bien ninguna de estas tres API admite archivos normales (es decir, archivos en un sistema de archivos), selecty pollpresentan esta falta de soporte como reportando descriptores como siempre legibles y escribibles siempre. Esto los hace inadecuados para cualquier tipo significativo de E / S del sistema de archivos sin bloqueo, un programa que usa selecto polly se encuentra con un descriptor de archivo del sistema de archivos al menos continuará funcionando (o si falla, no será porque de selecto poll), aunque quizás no con el mejor rendimiento.

Por otro lado, epollfallará rápidamente con un error ( EPERMaparentemente) cuando se le solicite monitorear dicho descriptor de archivo. Estrictamente hablando, esto no es incorrecto. Simplemente indica su falta de apoyo de manera explícita. Normalmente, aplaudiría las condiciones de falla explícitas, pero esta no está documentada (por lo que puedo decir) y da como resultado una aplicación completamente rota, en lugar de una que simplemente opera con un rendimiento potencialmente degradado.

En la práctica, el único lugar en el que he visto esto es cuando interactúo con stdio. Un usuario puede redirigir stdin o stdout desde / hacia un archivo normal. Mientras que anteriormente stdin y stdout habrían sido una tubería, compatible con epoll sin problemas, luego se convierte en un archivo normal y epoll falla ruidosamente, rompiendo la aplicación.

Jean-Paul Calderone
fuente
Muy buena respuesta. ¿Considere ser explícito sobre el comportamiento de pollpara completar?
quark
6
Mis dos centavos sobre el comportamiento de la lectura de archivos ordinarios: en general, prefiero el fracaso absoluto a la degradación del rendimiento. La razón es que es mucho más probable que se detecte durante el desarrollo y, por lo tanto, se solucione correctamente (por ejemplo, al tener un método alternativo para realizar la E / S para archivos reales). YMMV, por supuesto: puede que no haya una desaceleración notable, en cuyo caso la falla no es mejor. Pero la dramática desaceleración que ocurre solo en casos especiales puede ser muy difícil de detectar durante el desarrollo, dejándola como una bomba de tiempo cuando realmente se implementa.
quark
1
Solo tengo que leer completamente tu edición. En cierto sentido, estoy de acuerdo en que probablemente no sea correcto que epoll no imite a sus predecesores, pero de nuevo puedo imaginar que el desarrollador que implementó el error EPERM pensó "Solo porque siempre se ha roto, no hace que sea correcto romper el mío como bien." Y otro argumento en contra, soy un programador defensivo, cualquier cosa más allá de 1 + 1 es sospechosa y codifico de tal manera que permite fallas agradables. Tener el kernel disparando un error fuera de lo esperado no es agradable ni considerado.
David
1
@ Jean-Paul, ¿podrías agregar alguna explicación sobre kqueue también?
Buena persona
Dejando de lado el rendimiento, ¿hay man selectalgún problema resultante de esto? El kernel de Linux no impone un límite fijo, pero la implementación de glibc hace que fd_set sea un tipo de tamaño fijo, con FD_SETSIZE definido como 1024, y las macros FD _ * () operando de acuerdo con ese límite. Para monitorear descriptores de archivo mayores que 1023, use poll (2) en su lugar. En CentOS 7 ya he visto problemas en los que mi propio código falló en un select () porque el kernel devolvió un identificador de archivo> 1023 y actualmente estoy viendo un problema que huele a que Twisted puede tener el mismo problema.
Paul D Smith
4

En las pruebas en mi empresa, surgió un problema con epoll (), por lo tanto, un costo único en comparación con el de selección.

Cuando se intenta leer desde la red con un tiempo de espera, crear un epoll_fd (en lugar de un FD_SET) y agregar el fd al epoll_fd es mucho más caro 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 valores de fd en los 10,000, select seguía siendo un ganador. Estos son casos en los que solo hay un fd en el que un hilo está esperando, y simplemente tratando de superar el hecho de que la lectura de red y la escritura de red no se agotan cuando se usa un modelo de hilo de bloqueo. Por supuesto, los modelos de subprocesos de bloqueo tienen un rendimiento bajo en comparación con los sistemas de reactores sin bloqueo, pero hay ocasiones en las que, para integrarse con una base de código heredada particular, es necesario.

Este tipo de caso de uso es poco común en aplicaciones de alto rendimiento, porque un modelo de reactor no necesita crear un nuevo epoll_fd cada vez. Para el modelo en el que un epoll_fd es de larga duración, que es claramente preferido para cualquier diseño de servidor de alto rendimiento, epoll es el claro ganador en todos los sentidos.

Brian Bulkowski
fuente
5
Pero ni siquiera puede usar select()si tiene valores de descriptor de archivo en el rango de 10k +, a menos que recompile la mitad de su sistema para cambiar FD_SETSIZE, así que me pregunto cómo funcionó esta estrategia. Para el escenario que describió, probablemente vería poll()cuál es mucho más parecido select()de lo que es epoll(), pero elimina la limitación FD_SETSIZE.
Jean-Paul Calderone
Puede usar select () si tiene valores de descriptor de archivo en el rango de 10K, porque puede malloc () un FD_SET. De hecho, dado que FD_SETSIZE es tiempo de compilación y el límite real de fd está en tiempo de ejecución, el ÚNICO uso seguro de FD_SET verifica el número del descriptor de archivo con el tamaño del FD_SET, y hace un malloc (o equivalente moral) si el FD_SET es demasiado pequeña. Me sorprendió ver esto en producción con un cliente. Después de programar sockets durante 20 años, todo el código que había escrito, y la mayoría de los tutoriales en la web, no son seguros.
Brian Bulkowski
5
Esto no es cierto, hasta donde yo sé, en ninguna plataforma popular. FD_SETSIZEes una constante de tiempo de compilación establecida cuando se compila su biblioteca C. Si lo define con un valor diferente cuando construye su aplicación, entonces su aplicación y la biblioteca C no estarán de acuerdo y las cosas irán mal. Si tiene referencias que afirman que es seguro redefinir FD_SETSIZE, me interesaría verlas.
Jean-Paul Calderone