it-swarm-pt.tech

AE/S sem bloqueio é realmente mais rápida que a E/S de bloqueio multiencadeada? Como?

Eu procurei na web alguns detalhes técnicos sobre o bloqueio de E/S de entrada/saída e não-bloqueio e eu encontrei várias pessoas afirmando que a E/S sem bloqueio seria mais rápida do que bloquear E/S. Por exemplo, em este documento .

Se eu usar bloqueio de E/S, então é claro que o segmento que está atualmente bloqueado não pode fazer mais nada ... Porque está bloqueado. Mas assim que um encadeamento começa a ser bloqueado, o sistema operacional pode alternar para outro encadeamento e não voltar atrás até que haja algo a ser feito para o encadeamento bloqueado. Portanto, enquanto houver outro thread no sistema que precise de CPU e não esteja bloqueado, não deverá haver mais tempo ocioso de CPU comparado a uma abordagem sem bloqueio baseada em eventos, está lá?

Além de reduzir o tempo de inatividade da CPU, vejo mais uma opção para aumentar o número de tarefas que um computador pode executar em um determinado período de tempo: Reduzir a sobrecarga introduzida pela troca de threads. Mas como isso pode ser feito? E a sobrecarga é grande o suficiente para mostrar efeitos mensuráveis? Aqui está uma ideia de como posso visualizá-lo funcionando:

  1. Para carregar o conteúdo de um arquivo, um aplicativo delega essa tarefa a uma estrutura de E/S baseada em evento, passando uma função de retorno de chamada junto com um nome de arquivo
  2. A estrutura de evento delega para o sistema operacional, que programa um controlador DMA do disco rígido para gravar o arquivo diretamente na memória
  3. A estrutura de eventos permite que mais códigos sejam executados.
  4. Após a conclusão da cópia de disco para memória, o controlador DMA causa uma interrupção.
  5. O manipulador de interrupções do sistema operacional notifica a estrutura de E/S baseada em eventos sobre o carregamento completo do arquivo na memória. Como isso acontece? Usando um sinal?
  6. O código atualmente executado no evento i/o framework é concluído.
  7. A estrutura de E/S baseada em eventos verifica sua fila e vê a mensagem do sistema operacional da etapa 5 e executa o retorno de chamada obtido na etapa 1.

É assim que funciona? Se isso não acontecer, como funciona? Isso significa que o sistema de eventos pode funcionar sem nunca ter que tocar explicitamente na pilha (como um planejador real que precisaria fazer backup da pilha e copiar a pilha de outro encadeamento na memória durante a troca de encadeamentos)? Quanto tempo isso realmente salva? Existe mais do que isso?

101
yankee

A maior vantagem de E/S não bloqueantes ou assíncronas é que seu thread pode continuar funcionando paralelamente. Claro que você pode conseguir isso também usando um thread adicional. Como você afirmou para o melhor desempenho geral (sistema), eu acho que seria melhor usar E/S assíncronas e não vários threads (reduzindo a troca de threads).

Vamos examinar possíveis implementações de um programa de servidor de rede que deve manipular 1.000 clientes conectados em paralelo:

  1. Um thread por conexão (pode estar bloqueando E/S, mas também pode ser E/S sem bloqueio).
    Cada thread requer recursos de memória (também memória do kernel!), O que é uma desvantagem. E cada thread adicional significa mais trabalho para o planejador.
  2. Um thread para todas as conexões.
    Isso exige do sistema porque temos menos threads. Mas também evita que você use o desempenho total da sua máquina, porque você pode acabar levando um processador a 100% e deixando todos os outros processadores inativos.
  3. Alguns segmentos onde cada thread lida com algumas das conexões.
    Isso leva carga do sistema porque há menos threads. E pode usar todos os processadores disponíveis. No Windows, essa abordagem é suportada por API do Pool de Segmentos .

Claro que ter mais threads não é, por si só, um problema. Como você deve ter reconhecido, escolhi um número bastante alto de conexões/threads. Eu duvido que você verá alguma diferença entre as três implementações possíveis se estivermos falando apenas de uma dúzia de threads (isso também é o que Raymond Chen sugere no post do blog do MSDN O Windows tem um limite de 2000 threads por processo? ).

No Windows usando E/S de arquivo sem buffer significa que as gravações devem ter um tamanho que seja múltiplo do tamanho da página. Eu não testei isso, mas parece que isso também pode afetar positivamente o desempenho de gravação para gravações síncronas e assíncronas armazenadas em buffer.

Os passos de 1 a 7 que você descreve dão uma boa idéia de como isso funciona. No Windows, o sistema operacional informará sobre a conclusão de uma E/S assíncrona (WriteFile com OVERLAPPED structure) usando um evento ou um retorno de chamada. As funções de retorno de chamada serão chamadas apenas por exemplo quando seu código chamar WaitForMultipleObjectsEx com bAlertable definido como true.

Mais algumas leituras na web:

37
Werner Henze

AE/S inclui vários tipos de operações, como ler e gravar dados de discos rígidos, acessar recursos de rede, chamar serviços da Web ou recuperar dados de bancos de dados. Dependendo da plataforma e do tipo de operação, a E/S assíncrona geralmente aproveitará qualquer hardware ou suporte de sistema de baixo nível para executar a operação. Isso significa que ele será executado com o menor impacto possível na CPU.

No nível do aplicativo, a E/S assíncrona evita que os threads precisem aguardar pela conclusão das operações de E/S. Assim que uma operação de E/S assíncrona é iniciada, ela libera o encadeamento no qual foi lançada e um retorno de chamada é registrado. Quando a operação é concluída, o retorno de chamada é enfileirado para execução no primeiro encadeamento disponível.

Se a operação de E/S for executada de forma síncrona, ela manterá o encadeamento em execução sem fazer nada até que a operação seja concluída. O tempo de execução não sabe quando a operação de E/S é concluída, portanto, ele fornecerá periodicamente algum tempo de CPU para o encadeamento em espera, tempo de CPU que poderia ser usado por outros encadeamentos que possuem operações reais de CPU a serem executadas.

Portanto, como mencionado por @ user1629468, a E/S assíncrona não oferece melhor desempenho, mas sim melhor escalabilidade. Isso é óbvio quando executado em contextos que têm um número limitado de encadeamentos disponíveis, como é o caso dos aplicativos da web. O aplicativo da Web geralmente usa um conjunto de encadeamentos do qual eles atribuem encadeamentos a cada solicitação. Se as solicitações forem bloqueadas em operações de E/S de execução longa, haverá o risco de esgotar o pool da Web e congelar o aplicativo da Web ou retardar a resposta.

Uma coisa que notei é que a E/S assíncrona não é a melhor opção ao lidar com operações de E/S muito rápidas. Nesse caso, o benefício de não manter um encadeamento ocupado enquanto aguarda a conclusão da operação de E/S não é muito importante e o fato de a operação ser iniciada em um encadeamento e concluída em outro adiciona uma sobrecarga à execução geral.

Você pode ler uma pesquisa mais detalhada que fiz recentemente sobre o tópico de E/S assíncrona versus multithreading aqui .

26
Florin Dumitrescu

O principal motivo para usar o AIO é a escalabilidade. Quando visto no contexto de alguns tópicos, os benefícios não são óbvios. Mas quando o sistema for dimensionado para milhares de threads, a AIO oferecerá um desempenho muito melhor. A ressalva é que a biblioteca AIO não deve introduzir mais gargalos.

4
fissurezone

Para presumir uma melhoria de velocidade devido a qualquer forma de computação múltipla, você deve presumir que várias tarefas baseadas em CPU estão sendo executadas simultaneamente em vários recursos de computação (geralmente núcleos de processador) ou que nem todas as tarefas dependem do uso simultâneo de o mesmo recurso - isto é, algumas tarefas podem depender de um subcomponente do sistema (armazenamento em disco, digamos) enquanto algumas tarefas dependem de outro (recebendo comunicação de um dispositivo periférico) e outras ainda podem exigir o uso de núcleos de processador.

O primeiro cenário é geralmente chamado de programação "paralela". O segundo cenário é geralmente chamado de programação "concorrente" ou "assíncrona", embora "simultâneo" também seja usado para se referir ao caso de simplesmente permitir que um sistema operacional intercale a execução de várias tarefas, independentemente de tal execução precisar ser executada. colocar em série ou se vários recursos podem ser usados ​​para alcançar a execução paralela. Neste último caso, "concorrente" geralmente se refere à forma como a execução é escrita no programa, e não a partir da perspectiva da simultaneidade real da execução da tarefa.

É muito fácil falar sobre tudo isso com suposições tácitas. Por exemplo, alguns são rápidos em fazer uma declaração como "A/S assíncrona será mais rápida que E/S multi-threaded". Esta afirmação é duvidosa por várias razões. Primeiro, pode ser que algumas estruturas de E/S assíncronas sejam implementadas precisamente com multi-threading, caso em que elas são uma na mesma e não faz sentido dizer que um conceito "é mais rápido que" o outro . 

Em segundo lugar, mesmo no caso em que há uma implementação single-threaded de uma estrutura assíncrona (como um loop de eventos single-threaded) você ainda deve fazer uma suposição sobre o que esse loop está fazendo. Por exemplo, uma coisa tola que você pode fazer com um loop de eventos de encadeamento único é a solicitação para concluir de forma assíncrona duas tarefas diferentes relacionadas à CPU. Se você fez isso em uma máquina com apenas um núcleo de processador idealizado (ignorando as otimizações de hardware modernas), executar essa tarefa "de forma assíncrona" não seria muito diferente de executá-la com dois threads gerenciados independentemente ou com apenas um processo único - a diferença pode estar relacionada à troca de contexto de thread ou às otimizações de programação do sistema operacional, mas se ambas as tarefas forem para a CPU, seria semelhante em ambos os casos.

É útil imaginar muitos dos casos incomuns ou estúpidos que você pode encontrar.

"Assíncrono" não precisa ser concorrente, por exemplo, como acima: você "assíncrona" executa duas tarefas limitadas pela CPU em uma máquina com exatamente um núcleo de processador.

A execução multiencadeada não precisa ser simultânea: você gera dois encadeamentos em uma máquina com um único núcleo de processador ou solicita dois encadeamentos para adquirir qualquer outro tipo de recurso escasso (imagine, digamos, um banco de dados de rede que só pode estabelecer um conexão de cada vez). A execução dos encadeamentos pode ser interleaved, entretanto, o planejador do sistema operacional considera adequado, mas seu tempo de execução total não pode ser reduzido (e será aumentado a partir da alternância de contexto de encadeamento) em um único núcleo (ou mais geralmente, se você gerar mais encadeamentos do que núcleos para executá-los, ou ter mais encadeamentos solicitando um recurso do que o que o recurso pode sustentar). Essa mesma coisa vale para o multiprocessamento também.

Portanto, nem E/S assíncrona nem multi-threading tem que oferecer qualquer ganho de desempenho em termos de tempo de execução. Eles podem até retardar as coisas.

Se você definir um caso de uso específico, no entanto, como um programa específico que faz uma chamada de rede para recuperar dados de um recurso conectado à rede como um banco de dados remoto e também faz alguma computação local limitada pela CPU, pode começar a raciocinar sobre as diferenças de desempenho entre os dois métodos, considerando uma suposição específica sobre hardware.

As perguntas a fazer: Quantas etapas computacionais eu preciso executar e quantos sistemas independentes de recursos existem para realizá-las? Existem subconjuntos das etapas computacionais que exigem o uso de subcomponentes independentes do sistema e podem se beneficiar disso ao mesmo tempo? Quantos núcleos de processador eu tenho e qual é a sobrecarga para usar vários processadores ou threads para concluir tarefas em núcleos separados?

Se suas tarefas dependem amplamente de subsistemas independentes, uma solução assíncrona pode ser boa. Se o número de encadeamentos necessários para manipulá-lo for grande, de modo que a comutação de contexto se torne não-trivial para o sistema operacional, uma solução assíncrona de encadeamento único pode ser melhor. 

Sempre que as tarefas estiverem vinculadas pelo mesmo recurso (por exemplo, várias necessidades acessarem simultaneamente a mesma rede ou recurso local), o recurso de multiencadeamento provavelmente apresentará sobrecarga insatisfatória e, enquanto o assincronia single-threaded may introduzir menos sobrecarga, em essa situação limitada por recursos também não pode produzir uma aceleração. Nesse caso, a única opção (se você quiser uma aceleração) é disponibilizar várias cópias desse recurso (por exemplo, vários núcleos de processador se o recurso escasso for CPU; um banco de dados melhor que ofereça suporte a mais conexões simultâneas se o recurso escasso é um banco de dados limitado por conexão, etc.).

Outra maneira de colocar isso é: permitir que o sistema operacional intercale o uso de um único recurso para duas tarefas não seja mais rápido do que simplesmente permitir que uma tarefa use o recurso enquanto a outra aguarda, deixando a segunda concluir em série. Além disso, o custo do agendador de intercalação significa, em qualquer situação real, que ele realmente cria uma desaceleração. Não importa se o uso intercalado ocorre da CPU, um recurso de rede, um recurso de memória, um dispositivo periférico ou qualquer outro recurso do sistema. 

3
ely

Uma implementação possível de E/S sem bloqueio é exatamente o que você disse, com um conjunto de encadeamentos em segundo plano que bloqueia E/S e notifica o encadeamento do originador da E/S por meio de algum mecanismo de retorno de chamada. Na verdade, é assim que funciona o módulo AIO na glibc. Aqui são alguns detalhes vagos sobre a implementação.

Embora esta seja uma boa solução bastante portátil (contanto que você tenha threads), o sistema operacional normalmente é capaz de realizar serviços de E/S sem bloqueios com mais eficiência. Este artigo da Wikipedia lista possíveis implementações além do conjunto de encadeamentos.

2
Miguel

Atualmente, estou no processo de implementar o async io em uma plataforma integrada usando protothreads. O non-blocking io faz a diferença entre correr a 16000fps e 160fps. O maior benefício do non blocking io é que você pode estruturar seu código para fazer outras coisas enquanto o hardware faz o seu trabalho. Até mesmo a inicialização de dispositivos pode ser feita em paralelo. 

Martin

2
user2826084

Deixe-me dar um contra-exemplo de que a E/S assíncrona não funciona. Estou escrevendo um proxy semelhante ao abaixo - usando boost :: asio. https: // github. com/ArashPartow/proxy/blob/master/tcpproxy_server.cpp

No entanto, o cenário do meu caso é que as mensagens de entrada (do lado do cliente) são rápidas enquanto a saída (para o lado do servidor) é lenta para uma sessão, para manter a velocidade de entrada ou para maximizar o throughput total do proxy, temos que usar várias sessões em uma conexão.

Portanto, essa estrutura de E/S assíncrona não funciona mais. Precisamos de um pool de threads para enviar para o servidor, atribuindo a cada thread uma sessão.

0
Zhidian Du

A melhoria, até onde eu sei, é que a E/S assíncrona usa (estou falando sobre o MS System, apenas para esclarecer) o so chamado de portas de conclusão de E/S . Usando a chamada Assíncrona, a estrutura utiliza essa arquitetura automaticamente, e isso é muito mais eficiente do que o mecanismo padrão de threading. Como experiência pessoal, posso dizer que você sentiria a sua aplicação mais reativa se preferir o AsyncCalls em vez de bloquear os threads.

0
Felice Pollano

No Node, vários encadeamentos estão sendo iniciados, mas é uma camada inativa no tempo de execução do C++. 

"Sim, o NodeJS é single threaded, mas isso é uma meia verdade, na verdade é orientado a eventos e single-threaded com workers de background. O loop de evento principal é single-threaded mas a maioria dos trabalhos de I/O são executados em threads separados, porque as APIs de E/S no Node.js são assíncronas/não-bloqueantes por design, para acomodar o loop de eventos. "

https://codeburst.io/how-node-js-single-thread-mechanism-work-understanding-event-loop-in-nodejs-230f7440b0ea

"O Node.js não é bloqueado, o que significa que todas as funções (retornos de chamada) são delegadas ao loop de eventos e são (ou podem ser) executadas por threads diferentes. Isso é tratado pelo tempo de execução do Node.js."

https://itnext.io/multi-threading-and-multi-process-in-node-js-ffa5bb5cde98

A explicação "Node é mais rápido porque não bloqueia ..." é um pouco de marketing e essa é uma ótima pergunta. É eficiente e escalonável, mas não exatamente com encadeamento único.

0
SmokestackLightning