Eu estava assistindo essa palestra sobre a implementação de Async IO no Rust e Carl menciona dois modelos em potencial. Prontidão e conclusão.
Modelo de prontidão:
- você diz ao kernel que deseja ler de um soquete
- faça outras coisas por um tempo…
- o kernel informa quando o soquete está pronto
- você lê (preenche um buffer)
- faça o que precisar
- libere o buffer (acontece automaticamente com o Rust)
Modelo de conclusão:
- você aloca um buffer para o kernel preencher
- faça outras coisas por um tempo…
- o kernel informa quando o buffer foi preenchido
- faça o que precisar com os dados
- liberte o buffer
No exemplo de Carl de usar o modelo de prontidão, você poderia iterar sobre soquetes prontos, preenchendo e liberando um buffer global, o que faz parecer que ele usaria muito menos memória.
Agora minhas suposições:
Sob o capô (no espaço do kernel), quando se diz que um soquete está "pronto", os dados já existem. Ele entrou no soquete pela rede (ou de qualquer lugar) e o sistema operacional está mantendo os dados.
Não é como se essa alocação de memória magicamente não acontecesse no modelo de prontidão. É só que o sistema operacional está abstraindo de você. No modelo de conclusão, o sistema operacional está solicitando que você aloque memória antes que os dados entrem e é óbvio o que está acontecendo.
Aqui está minha versão alterada do modelo de prontidão:
- você diz ao kernel que deseja ler de um soquete
- faça outras coisas por um tempo…
- ALTERAÇÃO: os dados chegam ao sistema operacional (algum lugar na memória do kernel)
- o kernel informa que o soquete está pronto
- você lê (preencha outro buffer separado do buffer do kernel acima (ou você recebe um ponteiro para ele?))
- faça o que precisar
- libere o buffer (acontece automaticamente com o Rust)
/ Minhas suposições
Por acaso, gosto de manter o programa de espaço do usuário pequeno, mas eu só queria alguns esclarecimentos sobre o que realmente está acontecendo aqui. Não vejo que um modelo utilize inerentemente menos memória ou suporte a um nível mais alto de E / S simultânea. Eu adoraria ouvir pensamentos e explicações mais profundas sobre isso.
Respostas:
No modelo de prontidão, o consumo de memória é proporcional à quantidade de dados não consumidos pelo aplicativo.
No modelo de conclusão, o consumo de memória é proporcional à quantidade de chamadas de soquete pendentes.
Se houver muitos soquetes ociosos, o modelo de prontidão consumirá menos memória.
Há uma correção fácil para o Modelo de Conclusão: Inicie uma leitura de 1 byte. Isso consome apenas um pequeno buffer. Quando a leitura é concluída, emite outra leitura (talvez síncrona) que obtém o restante dos dados.
Em alguns idiomas, o Modelo de Conclusão é extremamente simples de implementar. Considero que é uma boa opção padrão.
fonte
Mas o que acontece se houver mais dados do que você alocou espaço? O kernel ainda precisa alocar seu próprio buffer para não descartar os dados. (Por exemplo, é por isso que o truque de leitura de 1 byte mencionado na resposta de usr funciona.)
A desvantagem é que, embora o Modelo de Conclusão consuma mais memória, ele também pode (às vezes) executar menos operações de cópia, porque manter o buffer ao redor significa que o hardware pode fazer DMA diretamente dentro ou fora dele. Também suspeito (mas tenho menos certeza) de que o Modelo de Conclusão tende a executar a operação de cópia real (quando existe) em outro encadeamento, pelo menos para o IOCP do Windows, enquanto o Modelo de Prontidão o faz como parte do processo não-bloqueador
read()
ouwrite()
ligar.fonte