Advertências de selecionar / enquete vs. reatores epoll em Twisted

95

Tudo o que li e experimentei (aplicativos baseados em Tornado) me leva a acreditar que o ePoll é um substituto natural para a rede baseada em Select e Poll, especialmente com Twisted. O que me deixa paranóico, é muito raro que uma técnica ou metodologia melhor não tenha um preço.

Ler algumas dezenas de comparações entre epoll e alternativas mostra que epoll é claramente o campeão em velocidade e escalabilidade, especificamente que escala de forma linear, o que é fantástico. Dito isso, e quanto à utilização do processador e da memória, a epoll ainda é a campeã?

David
fonte

Respostas:

190

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 selectchamada é 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. selectconsegue 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 selectsignifica 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 selectse 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 selectnão funciona.

Aleatoriamente, também descobri recentemente uma ligeira desvantagem de epollem comparação com selectou poll. Embora nenhuma dessas três APIs ofereça suporte a arquivos normais (ou seja, arquivos em um sistema de arquivos), selecte pollapresente 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 selectou polle acontece de encontrar um descritor de arquivo do sistema de arquivos pelo menos continuará a operar (ou se falhar, não será porque de selectou poll), embora talvez não com o melhor desempenho.

Por outro lado, epollfalhará rapidamente com um erro ( EPERMaparentemente) 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.

Jean-Paul Calderone
fonte
Muito boa resposta. Considere ser explícito sobre o comportamento de pollpara integridade?
quark de
6
Meus dois centavos sobre o comportamento de leitura de arquivos comuns: geralmente prefiro o fracasso total à degradação do desempenho. A razão é que é muito mais provável que seja detectado durante o desenvolvimento e, portanto, contornado de maneira adequada (digamos, tendo um método alternativo de fazer E / S para arquivos reais). YMMV, claro: pode não haver desaceleração perceptível, caso em que a falha não é melhor. Mas a desaceleração dramática que ocorre apenas em casos especiais pode ser muito difícil de detectar durante o desenvolvimento, deixando-a como uma bomba-relógio quando realmente implantada.
quark de
1
Acabei de ler sua edição completamente. Em certo sentido, eu concordo que provavelmente não é certo que epoll não imite seus antecessores, mas, novamente, posso imaginar o dev que implementou o erro de EPERM pensou "Só porque sempre foi quebrado, não é correto quebrar o meu como bem." E ainda outro contra-argumento, eu sou um programador defensivo que qualquer coisa além de 1 + 1 é suspeito e eu codifico de forma a permitir falhas normais. Fazer o kernel disparar um erro fora de expectativa não é bom ou atencioso.
David
1
@Jean-Paul você poderia acrescentar alguma explicação sobre o kqueue também?
Boa pessoa
Deixando de lado o desempenho, existe um problema resultante disso (de 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.
Paul D Smith
4

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.

Brian Bulkowski
fonte
5
Mas você não pode nem usar 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 examinaria poll()qual é muito mais parecido select()do que parecido epoll()- mas remove a limitação de FD_SETSIZE.
Jean-Paul Calderone
Você pode usar select () se tiver valores de descritor de arquivo na faixa de 10K, porque você pode malloc () um FD_SET. Na verdade, uma vez que FD_SETSIZE é o tempo de compilação e o limite real do fd está no tempo de execução, o ÚNICO uso seguro de FD_SET verifica o número do descritor de arquivo em relação ao tamanho do FD_SET e faz um malloc (ou equivalente moral) se o FD_SET é muito pequeno. Fiquei chocado quando vi isso em produção com um cliente. Depois de programar soquetes por 20 anos, todo o código que eu já escrevi - e a maioria dos tutoriais na web - não são seguros.
Brian Bulkowski
5
Isso não é verdade, até onde eu sei, em nenhuma plataforma popular. 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_SETSIZEestou interessado em vê-las.
Jean-Paul Calderone