Pergunta

Gostaria de fazer uma pergunta e depois dar minha própria resposta, mas também ver quais respostas outras pessoas têm.

Temos dois arquivos grandes que gostaríamos de ler de dois threads separados simultaneamente.Um thread lerá sequencialmente o arquivoA enquanto o outro thread lerá sequencialmente o arquivoB.Não há bloqueio ou comunicação entre os threads, ambos estão lendo sequencialmente o mais rápido que podem e descartando imediatamente os dados que lêem.

Nossa experiência com esta configuração no Windows é muito ruim.A taxa de transferência combinada dos dois threads é da ordem de 2-3 MiB/s.A unidade parece passar a maior parte do tempo indo e voltando entre os dois arquivos, provavelmente lendo muito pouco após cada busca.

Se desabilitarmos um dos threads e observarmos temporariamente o desempenho de um único thread, obteremos uma largura de banda muito melhor (~45 MiB/s para esta máquina).Claramente, o mau desempenho de dois threads é um artefato do agendador de disco do sistema operacional.

Há algo que possamos fazer para melhorar o desempenho da leitura simultânea de threads? Talvez usando APIs diferentes ou ajustando de alguma forma os parâmetros do agendador de disco do sistema operacional.

Alguns detalhes:

Os arquivos são da ordem de 2 GiB cada em uma máquina com 2GiB de RAM.Para efeitos desta questão, consideramos que eles não estão em cache e estão perfeitamente desfragmentados.Usamos ferramentas de desfragmentação e reinicializamos para garantir que esse seja o caso.

Não estamos usando APIs especiais para ler esses arquivos.O comportamento pode ser repetido em várias APIs padrão do Bog, como CreateFile do Win32, fopen do C, std::ifstream do C++, FileInputStream do Java, etc.

Cada thread gira em um loop fazendo chamadas para a função read.Variamos o número de bytes solicitados da API em cada iteração de valores entre 1KiB até 128MiB.Variar isso não teve efeito, então claramente a quantidade que o sistema operacional lê fisicamente após cada busca de disco não é ditada por esse número.Isto é exatamente o que deveria ser esperado.

A diferença dramática entre o desempenho de um thread e de dois threads pode ser repetida no Windows 2000, no Windows XP (32 bits e 64 bits), no Windows Server 2003 e também com e sem RAID5 de hardware.

Foi útil?

Solução

O problema parece estar na política de agendamento de E/S do Windows.De acordo com o que descobri aqui há muitas maneiras de um sistema operacional.para agendar solicitações de disco.Embora o Linux e outros possam escolher entre diferentes políticas, antes do Vista o Windows estava bloqueado em uma única política:uma fila FIFO, onde todas as solicitações foram divididas em blocos de 64 KB.Acredito que esta política é a causa do problema que você está enfrentando:o agendador irá misturar solicitações dos dois threads, causando busca contínua entre diferentes áreas do disco.
Agora, a boa notícia é que, de acordo com aqui e aqui, o Vista introduziu um agendador de disco mais inteligente, onde você pode definir a prioridade de suas solicitações e também alocar uma largura de banda mínima para o seu processo.
A má notícia é que não encontrei nenhuma maneira de alterar a política de disco ou o tamanho dos buffers nas versões anteriores do Windows.Além disso, mesmo que aumentar a prioridade de E/S do disco do seu processo aumente o desempenho em relação aos outros processos, você ainda terá problemas de competição entre seus threads.
O que posso sugerir é modificar seu software introduzindo uma política de acesso ao disco criada por você mesmo.
Por exemplo, você poderia usar uma política como esta em seu thread B (semelhante para Thread A):

if THREAD A is reading from disk then wait for THREAD A to stop reading or wait for X ms
Read for X ms (or Y MB)
Stop reading and check status of thread A again  

Você pode usar semáforos para verificação de status ou usar contadores perfmon para obter o status da fila de disco real.Os valores de X e/ou Y também podem ser ajustados automaticamente verificando as taxas de transferência reais e modificando-as lentamente, maximizando assim o rendimento quando o aplicativo é executado em máquinas e/ou sistemas operacionais diferentes.Você pode descobrir que os níveis de cache, memória ou RAID os afetam de uma forma ou de outra, mas com o ajuste automático você sempre obterá o melhor desempenho em todos os cenários.

Outras dicas

Gostaria de acrescentar mais algumas notas à minha resposta.Todos os outros sistemas operacionais que não sejam da Microsoft que testamos não apresentam esse problema.Linux, FreeBSD e Mac OS X (este último em hardware diferente) degradam muito mais facilmente em termos de largura de banda agregada ao passar de um thread para dois.O Linux, por exemplo, degradou de ~45 MiB/seg para ~42 MiB/seg.Esses outros sistemas operacionais devem ler pedaços maiores do arquivo entre cada busca e, portanto, não gastar quase todo o tempo esperando no disco para buscar.

Nossa solução para Windows é passar o FILE_FLAG_NO_BUFFERING bandeira para CreateFile e use leituras grandes (~16MiB) em cada chamada para ReadFile.Isso não é ideal por vários motivos:

  • Os arquivos não são armazenados em cache quando lidos dessa forma, portanto, não há nenhuma das vantagens que o cache normalmente oferece.
  • As restrições ao trabalhar com este sinalizador são muito mais complicadas do que a leitura normal (alinhamento dos buffers de leitura aos limites da página, etc.).

(Como observação final.Isso explica por que a troca no Windows é tão infernal?Ou seja, o Windows é incapaz de executar IO em vários arquivos simultaneamente com qualquer eficiência; portanto, durante a troca, todas as outras operações de IO são forçadas a ser desproporcionalmente lentas.)


Edite para adicionar mais alguns detalhes para Will Dean:

É claro que através destas diferentes configurações de hardware os números brutos mudaram (às vezes substancialmente).O problema, entretanto, é a degradação consistente no desempenho que apenas o Windows sofre ao passar de um thread para dois.Aqui está um resumo das máquinas testadas:

  • Várias estações de trabalho Dell (Intel Xeon) de várias idades executando Windows 2000, Windows XP (32 bits) e Windows XP (64 bits) com unidade única.
  • Um servidor Dell 1U (Intel Xeon) executando Windows Server 2003 (64 bits) com RAID 1+0.
  • Uma estação de trabalho HP (AMD Opteron) com Windows XP (64 bits) e Windows Server 2003 e hardware RAID 5.
  • Meu PC doméstico sem marca (AMD Athlon64) executando Windows XP (32 bits), FreeBSD (64 bits) e Linux (64 bits) com unidade única.
  • Meu MacBook doméstico (Intel Core1) rodando Mac OS X, unidade SATA única.
  • Minha casa Koolu PC rodando Linux.Muito fraco em comparação com outros sistemas, mas demonstrei que mesmo esta máquina pode superar um servidor Windows com RAID5 ao fazer leituras de disco multithread.

O uso da CPU em todos esses sistemas foi muito baixo durante os testes e o antivírus foi desativado.

Esqueci de mencionar antes, mas também tentamos o Win32 normal CreateFile API com o FILE_FLAG_SEQUENTIAL_SCAN conjunto de bandeira.Este sinalizador não resolveu o problema.

Parece um pouco estranho que você não veja nenhuma diferença em uma ampla variedade de versões do Windows e nada entre uma única unidade e o hardware raid-5.

É apenas uma “intuição”, mas isso me faz duvidar que este seja realmente um problema simples de busca.Além do OS X e do Raid5, tudo isso foi tentado na mesma máquina - você tentou outra máquina?O uso da CPU é basicamente zero durante este teste?

Qual é o aplicativo mais curto que você pode escrever que demonstra esse problema?- Eu estaria interessado em experimentar aqui.

Eu criaria algum tipo de bloqueio seguro de thread na memória.Cada thread poderia esperar no bloqueio até que ele estivesse livre.Quando o bloqueio for liberado, pegue o bloqueio e leia o arquivo por um período de tempo definido ou uma quantidade definida de dados e, em seguida, libere o bloqueio para quaisquer outros threads em espera.

Você usa Portas IOCompletion no Windows?Windows via C++ tem um capítulo detalhado sobre esse assunto e, por sorte, também está disponível no MSDN.

Paul - vi a atualização.Muito interessante.

Seria interessante experimentá-lo no Vista ou no Win2008, já que as pessoas parecem estar relatando algumas melhorias consideráveis ​​de E/S em algumas circunstâncias.

Minha única sugestão sobre uma API diferente seria tentar mapear a memória dos arquivos - você já tentou isso?Infelizmente, com 2 GB por arquivo, você não conseguirá mapear vários arquivos inteiros em uma máquina de 32 bits, o que significa que isso não é tão trivial quanto poderia ser.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top