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 select
llamada 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. 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 la cantidad de descriptores de archivo que puede usar select
significa 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 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 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 select
no lo hace.
Al azar, también he descubierto recientemente un pequeño inconveniente en epoll
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 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 select
o poll
y se encuentra con un descriptor de archivo del sistema de archivos al menos continuará funcionando (o si falla, no será porque de select
o poll
), aunque quizás no con el mejor rendimiento.
Por otro lado, epoll
fallará rápidamente con un error ( EPERM
aparentemente) 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.
poll
para completar?man select
algú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.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.
fuente
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íapoll()
cuál es mucho más parecidoselect()
de lo que esepoll()
, pero elimina la limitación FD_SETSIZE.FD_SETSIZE
es 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 redefinirFD_SETSIZE
, me interesaría verlas.