Para um número muito pequeno de soquetes (varia dependendo do seu hardware, é claro, mas estamos falando de algo na ordem de 10 ou menos), o select pode superar o epoll em uso de memória e velocidade de execução. É claro que, para um número tão pequeno de soquetes, os dois mecanismos são tão rápidos que você realmente não se importa com essa diferença na grande maioria dos casos.
Porém, um esclarecimento. Ambos selecionam e dimensionam linearmente. Uma grande diferença, porém, é que as APIs voltadas para o espaço do usuário têm complexidades baseadas em coisas diferentes. O custo de uma select
chamada é aproximadamente igual ao valor do descritor de arquivo de maior numeração que você passa. Se você selecionar em um único fd, 100, isso é quase duas vezes mais caro do que selecionar em um único fd, 50. Adicionar mais fds abaixo do mais alto não é totalmente gratuito, então é um pouco mais complicado do que isso na prática, mas isso é uma boa primeira aproximação para a maioria das implementações.
O custo do epoll está mais próximo do número de descritores de arquivo que realmente contêm eventos. Se você está monitorando 200 descritores de arquivo, mas apenas 100 deles têm eventos neles, então você (quase) está pagando apenas por esses 100 descritores de arquivo ativos. É aqui que epoll tende a oferecer uma de suas principais vantagens em relação ao select. Se você tiver mil clientes que estão em sua maioria ociosos, quando usar select, ainda estará pagando por todos os mil deles. No entanto, com epoll, é como se você tivesse apenas alguns - você só está pagando por aqueles que estão ativos em um determinado momento.
Tudo isso significa que o epoll levará a menos uso da CPU para a maioria das cargas de trabalho. No que diz respeito ao uso de memória, é um pouco complicado. select
consegue representar todas as informações necessárias de uma forma altamente compacta (um bit por descritor de arquivo). E a limitação FD_SETSIZE (normalmente 1024) em quantos descritores de arquivo você pode usar select
significa que você nunca vai gastar mais de 128 bytes para cada um dos três conjuntos de fd que você pode usar comselect
(ler, escrever, exceção). Comparado com aqueles 384 bytes máximos, epoll é uma espécie de porco. Cada descritor de arquivo é representado por uma estrutura multibyte. Porém, em termos absolutos, ainda não vai usar muita memória. Você pode representar um grande número de descritores de arquivo em algumas dezenas de kilobytes (cerca de 20k por 1000 descritores de arquivo, eu acho). E você também pode acrescentar o fato de que terá de gastar todos os 384 desses bytes com select
se quiser monitorar apenas um descritor de arquivo, mas seu valor for 1024, enquanto com epoll você gastaria apenas 20 bytes. Ainda assim, todos esses números são muito pequenos, então não faz muita diferença.
E há também aquele outro benefício do epoll, que talvez você já conheça, que não se limita aos descritores de arquivo FD_SETSIZE. Você pode usá-lo para monitorar quantos descritores de arquivo você tiver. E se você tiver apenas um descritor de arquivo, mas seu valor for maior que FD_SETSIZE, epoll funciona com ele também, mas select
não funciona.
Aleatoriamente, também descobri recentemente uma ligeira desvantagem de epoll
em comparação com select
ou poll
. Embora nenhuma dessas três APIs ofereça suporte a arquivos normais (ou seja, arquivos em um sistema de arquivos), select
e poll
apresente essa falta de suporte ao relatar tais descritores como sempre legíveis e sempre graváveis. Isso os torna inadequados para qualquer tipo significativo de I / O sem bloqueio de sistema de arquivos, um programa que usa select
ou poll
e acontece de encontrar um descritor de arquivo do sistema de arquivos pelo menos continuará a operar (ou se falhar, não será porque de select
ou poll
), embora talvez não com o melhor desempenho.
Por outro lado, epoll
falhará rapidamente com um erro ( EPERM
aparentemente) quando for solicitado a monitorar esse descritor de arquivo. A rigor, isso dificilmente é incorreto. Ele está apenas sinalizando sua falta de suporte de forma explícita. Normalmente, eu aplaudiria as condições de falha explícitas, mas esta não é documentada (pelo que posso dizer) e resulta em um aplicativo completamente quebrado, ao invés de um que simplesmente opera com desempenho potencialmente degradado.
Na prática, o único lugar em que vi isso acontecer foi ao interagir com o stdio. Um usuário pode redirecionar stdin ou stdout de / para um arquivo normal. Considerando que anteriormente stdin e stdout teriam sido um pipe - suportado por epoll perfeitamente - ele se torna um arquivo normal e epoll falha ruidosamente, quebrando o aplicativo.
poll
para integridade?man select
) O kernel do Linux não impõe limite fixo, mas a implementação glibc torna fd_set um tipo de tamanho fixo, com FD_SETSIZE definido como 1024, e as macros FD _ * () operando de acordo com esse limite. Para monitorar descritores de arquivo maiores que 1023, use poll (2). No CentOS 7 eu já vi problemas em que meu próprio código falhou em select () porque o kernel retornou um identificador de arquivo> 1023 e atualmente estou procurando um problema que cheira a ser Twisted tendo o mesmo problema.Em testes na minha empresa, surgiu um problema com epoll (), portanto, um único custo em comparação com o select.
Ao tentar ler da rede com um tempo limite, criar um epoll_fd (em vez de FD_SET) e adicionar fd ao epoll_fd é muito mais caro do que criar um FD_SET (que é um malloc simples).
De acordo com a resposta anterior, conforme o número de FDs no processo torna-se grande, o custo de select () torna-se maior, mas em nossos testes, mesmo com valores de fd na casa dos 10.000, select ainda foi um vencedor. Esses são os casos em que há apenas um fd que uma thread está aguardando e simplesmente tentando superar o fato de que a leitura e a gravação na rede não atingem o tempo limite ao usar um modelo de thread de bloqueio. Obviamente, os modelos de thread de bloqueio têm baixo desempenho em comparação com os sistemas de reator sem bloqueio, mas há ocasiões em que, para integrar com uma base de código legada específica, é necessário.
Esse tipo de caso de uso é raro em aplicativos de alto desempenho, porque um modelo de reator não precisa criar um novo epoll_fd todas as vezes. Para o modelo em que um epoll_fd tem longa duração - o que é claramente preferido para qualquer design de servidor de alto desempenho - o epoll é o vencedor em todos os sentidos.
fonte
select()
se tiver valores de descritor de arquivo na faixa de 10k + - a menos que você recompile metade do seu sistema para alterar FD_SETSIZE - então eu me pergunto como essa estratégia funcionou. Para o cenário que você descreveu, provavelmente examinariapoll()
qual é muito mais parecidoselect()
do que parecidoepoll()
- mas remove a limitação de FD_SETSIZE.FD_SETSIZE
é uma constante de tempo de compilação definida quando sua biblioteca C é compilada. Se você defini-lo com um valor diferente ao construir seu aplicativo, então seu aplicativo e a biblioteca C irão discordar e as coisas irão mal. Se você tiver referências afirmando que é seguro redefinir,FD_SETSIZE
estou interessado em vê-las.