2010-01-09 12 views
88

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.Advertencias de select/poll vs. reactores epoll en Twisted

La lectura de un par de docenas de comparaciones entre epoll y alternativas muestra que epoll es claramente el campeón de la velocidad y la escalabilidad, específicamente que escala de forma 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?

Respuesta

180

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 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 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 de FD_SETSIZE (normalmente 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 quiere 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 esa otra ventaja de epoll, que quizás ya conozca, 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 lo hace.

azar, También he descubierto recientemente un pequeño inconveniente a 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 presentan esta falta de soporte como informes 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, al parecer) cuando se le pida 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 interactuando 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.

+0

Muy buena respuesta. Considere ser explícito sobre el comportamiento de 'poll' para la integridad? – quark

+6

Mis dos centavos sobre el comportamiento de leer archivos ordinarios: generalmente prefiero la falla total 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 solucionó adecuadamente (por ejemplo, teniendo un método alternativo de hacer las E/S para los archivos reales). YMMV por supuesto: puede que no haya desaceleración notable en cuyo caso la falla no es mejor. Pero la desaceleración dramática que ocurre solo en casos especiales puede ser muy difícil de detectar durante el desarrollo, dejándola como una bomba de tiempo cuando se despliega. – quark

+1

Acabo de leer completamente tu edición. En cierto sentido, estoy de acuerdo en que probablemente no sea correcto para epoll no imitar a sus predecesores, pero de nuevo me puedo imaginar al desarrollador que implementó el error EPERM pensó: "El hecho de que siempre se haya roto no hace bien en romper el mío bien." Y aún otro contra argumento, soy un programador defensivo, cualquier cosa pasada 1 + 1 es sospechosa y codigo de tal manera que permita fracasos graciosos. Hacer que el núcleo emita un error de expectativa no es agradable ni considerado. – David

3

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, crear un archivo 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 valores fd en los 10.000, seleccionó sigue siendo 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 aplicaciones de alto rendimiento, ya que 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.

+5

Pero ni siquiera puede usar 'select()' si tiene valores de descriptor de archivo en el rango 10k +, a menos que recompile la mitad de su sistema para cambiar FD_SETSIZE, entonces me pregunto cómo funciona esta estrategia. Para el escenario que describió, probablemente miraría 'poll()' que se parece mucho más a 'select()' que a 'epoll()' - pero elimina la limitación de FD_SETSIZE. –

+0

Puede usar select() si tiene valores de descriptor de archivo en el rango 10K, porque puede malloc() un FD_SET. De hecho, dado que FD_SETSIZE es tiempo de compilación y el límite fd real es en tiempo de ejecución, el ÚNICO uso seguro de FD_SET verifica el número del descriptor de archivo contra el tamaño del FD_SET, y realiza un malloc (o un equivalente moral) si el FD_SET es demasiado pequeña. Me sorprendió cuando vi esto en producción con un cliente. Después de programar tomas de corriente durante 20 años, todo el código que he escrito, y la mayoría de los tutoriales en la web, no son seguros. –

+5

Esto no es cierto, hasta donde yo sé, en cualquier plataforma popular. 'FD_SETSIZE' es una constante de tiempo de compilación establecida cuando se compila su biblioteca * C *. Si define un valor diferente cuando construye su aplicación, entonces su aplicación y la biblioteca C estarán en desacuerdo y las cosas irán mal. Si tiene referencias que afirmen que es seguro redefinir 'FD_SETSIZE', estaría interesado en verlas. –