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.
Muy buena respuesta. Considere ser explícito sobre el comportamiento de 'poll' para la integridad? – quark
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
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