Como você determina o tamanho ideal do buffer ao usar o FileInputStream?

156

Eu tenho um método que cria um MessageDigest (um hash) de um arquivo e preciso fazer isso em muitos arquivos (> = 100.000). Qual o tamanho do buffer usado para ler os arquivos para maximizar o desempenho?

Quase todo mundo está familiarizado com o código básico (que repetirei aqui apenas por precaução):

MessageDigest md = MessageDigest.getInstance( "SHA" );
FileInputStream ios = new FileInputStream( "myfile.bmp" );
byte[] buffer = new byte[4 * 1024]; // what should this value be?
int read = 0;
while( ( read = ios.read( buffer ) ) > 0 )
    md.update( buffer, 0, read );
ios.close();
md.digest();

Qual é o tamanho ideal do buffer para maximizar a taxa de transferência? Eu sei que isso depende do sistema e tenho certeza de que depende do sistema operacional, do FileSystem e do HDD, e talvez haja outro hardware / software na mistura.

(Devo salientar que sou um pouco novo em Java, portanto, pode ser apenas uma chamada à API Java que eu não conheço.)

Edit: Eu não sei antecipadamente os tipos de sistemas em que serão usados, então não posso assumir muito. (Estou usando Java por esse motivo.)

Edit: O código acima está faltando coisas como try..catch para tornar a postagem menor

ARKBAN
fonte

Respostas:

213

O tamanho ideal do buffer está relacionado a várias coisas: tamanho do bloco do sistema de arquivos, tamanho do cache da CPU e latência do cache.

A maioria dos sistemas de arquivos está configurada para usar tamanhos de bloco de 4096 ou 8192. Em teoria, se você configurar o tamanho do buffer para ler alguns bytes a mais do que o bloco de disco, as operações com o sistema de arquivos podem ser extremamente ineficientes (por exemplo, se você configurou seu buffer para ler 4100 bytes de cada vez, cada leitura exigiria 2 leituras de bloco pelo sistema de arquivos). Se os blocos já estiverem no cache, você acaba pagando o preço da RAM -> latência do cache L3 / L2. Se você não tiver sorte e os blocos ainda não estiverem no cache, pagará o preço da latência do disco-> RAM também.

É por isso que você vê a maioria dos buffers dimensionados com uma potência de 2 e geralmente maior que (ou igual ao) tamanho do bloco de disco. Isso significa que uma de suas leituras de fluxo pode resultar em várias leituras de bloco de disco - mas essas leituras sempre usarão um bloco completo - nenhuma leitura desperdiçada.

Agora, isso é bastante compensado em um cenário típico de streaming, porque o bloco que é lido do disco ainda estará na memória quando você clicar na próxima leitura (afinal, estamos fazendo leituras sequenciais aqui) - então você acaba pagando o preço da latência do cache RAM -> L3 / L2 na próxima leitura, mas não a latência do disco-> RAM. Em termos de ordem de magnitude, a latência do disco-> RAM é tão lenta que praticamente inverte qualquer outra latência com a qual você esteja lidando.

Portanto, suspeito que, se você executou um teste com diferentes tamanhos de cache (provavelmente não o fez), provavelmente encontrará um grande impacto no tamanho do cache até o tamanho do bloco do sistema de arquivos. Acima disso, suspeito que as coisas se estabilizariam rapidamente.

Há uma tonelada condições e exceções aqui - as complexidades do sistema são realmente surpreendentes (apenas controlar as transferências de cache L3 -> L2 é incrivelmente complexo e muda com todos os tipos de CPU).

Isso leva à resposta do 'mundo real': se seu aplicativo estiver em 99%, defina o tamanho do cache como 8192 e siga em frente (melhor ainda, escolha o encapsulamento sobre o desempenho e use BufferedInputStream para ocultar os detalhes). Se você estiver no 1% dos aplicativos que são altamente dependentes da taxa de transferência do disco, elabore sua implementação para que você possa trocar diferentes estratégias de interação do disco e forneça os botões e discagens para permitir que seus usuários testem e otimizem (ou apresentem algumas sistema de auto-otimização).

Kevin Day
fonte
3
Fiz algumas marcações bancárias em um telefone celular (Nexus 5X) para o meu aplicativo Android: arquivos pequenos (3,5Mb) e arquivos grandes (175 Mb). E descobriu que o tamanho dourado seria um byte [] de 524288 comprimentos. Bem, você pode ganhar de 10 a 20 ms se alternar entre o pequeno buffer 4Kb e o grande buffer 524Kb, dependendo do tamanho do arquivo, mas não vale a pena. Então 524 Kb foi a melhor opção no meu caso.
precisa
19

Sim, provavelmente depende de várias coisas - mas duvido que faça muita diferença. Costumo optar por 16K ou 32K como um bom equilíbrio entre uso e desempenho da memória.

Observe que você deve ter um bloco try / finalmente no código para garantir que o fluxo seja fechado, mesmo que uma exceção seja lançada.

Jon Skeet
fonte
Eu editei o post sobre o try..catch. No meu código real, eu tenho um, mas deixei de fora para tornar a postagem mais curta.
ARKBAN 25/10/08
1
se quisermos definir um tamanho fixo, qual é o melhor? 4k, 16k ou 32k?
BattleTested
2
@MohammadrezaPanahi: Por favor, não use comentários para usuários de texugos. Você esperou menos de uma hora antes de um segundo comentário. Lembre-se de que os usuários podem dormir facilmente, ou em reuniões, ou basicamente ocupados com outras coisas e não têm obrigação de responder comentários. Mas, para responder à sua pergunta: depende inteiramente do contexto. Se você estiver executando em um sistema com muita restrição de memória, provavelmente precisará de um pequeno buffer. Se você estiver executando em um sistema grande, o uso de um buffer maior reduzirá o número de chamadas de leitura. A resposta de Kevin Day é muito boa.
Jon Skeet
7

Na maioria dos casos, isso realmente não importa muito. Basta escolher um bom tamanho, como 4K ou 16K, e ficar com ele. Se você acredita que esse é o gargalo do seu aplicativo, comece a criar perfis para encontrar o tamanho ideal do buffer. Se você escolher um tamanho muito pequeno, perderá tempo realizando operações de E / S e chamadas de funções extras. Se você escolher um tamanho muito grande, começará a ver muitas falhas de cache, o que realmente o tornará mais lento. Não use um buffer maior que o tamanho do cache L2.

Adam Rosenfield
fonte
4

No caso ideal, devemos ter memória suficiente para ler o arquivo em uma operação de leitura. Esse seria o melhor desempenho, porque deixamos o sistema gerenciar o Sistema de Arquivos, as unidades de alocação e o HDD à vontade. Na prática, você tem a sorte de conhecer os tamanhos de arquivo antecipadamente, basta usar o tamanho médio do arquivo arredondado para 4K (unidade de alocação padrão no NTFS). E o melhor de tudo: crie uma referência para testar várias opções.

Ovidiu Pacurar
fonte
você quer dizer que o melhor tamanho do buffer para leitura e gravação em um arquivo é 4k?
BattleTested
4

Você pode usar os BufferedStreams / readers e, em seguida, usar seus tamanhos de buffer.

Acredito que o BufferedXStreams esteja usando 8192 como o tamanho do buffer, mas, como Ovidiu disse, você provavelmente deve executar um teste em várias opções. Realmente vai depender das configurações do sistema de arquivos e do disco, quais são os melhores tamanhos.

John Gardner
fonte
4

A leitura de arquivos usando o FileChannel e o MappedByteBuffer do Java NIO provavelmente resultará em uma solução muito mais rápida do que qualquer solução que envolva o FileInputStream. Basicamente, mapeie arquivos grandes de memória e use buffers diretos para arquivos pequenos.

Alexander
fonte
4

Na fonte do BufferedInputStream você encontrará: private static int DEFAULT_BUFFER_SIZE = 8192;
Portanto, é bom você usar esse valor padrão.
Mas se você conseguir descobrir mais algumas informações, obterá respostas mais valiosas.
Por exemplo, seu adsl talvez prefira um buffer de 1454 bytes, devido à carga útil do TCP / IP. Para discos, você pode usar um valor que corresponda ao tamanho do bloco do seu disco.

GoForce5500
fonte
1

Como já mencionado em outras respostas, use BufferedInputStreams.

Depois disso, acho que o tamanho do buffer não importa. O programa está vinculado à E / S e o tamanho crescente do buffer em relação ao padrão BIS não causará grande impacto no desempenho.

Ou o programa é vinculado à CPU dentro do MessageDigest.update () e a maior parte do tempo não é gasta no código do aplicativo, portanto, ajustá-lo não ajudará.

(Hmm ... com vários núcleos, os threads podem ajudar.)

Maglob
fonte
0

1024 é apropriado para uma ampla variedade de circunstâncias, embora na prática você possa ver melhor desempenho com um tamanho de buffer maior ou menor.

Isso dependeria de vários fatores, incluindo o tamanho do bloco do sistema de arquivos e o hardware da CPU.

Também é comum escolher uma potência de 2 para o tamanho do buffer, uma vez que a maioria dos hardwares subjacentes é estruturada com tamanhos de bloco e cache com capacidade de 2. As classes Buffered permitem especificar o tamanho do buffer no construtor. Se nenhum for fornecido, eles usarão um valor padrão, que é uma potência de 2 na maioria das JVMs.

Independentemente de qual tamanho de buffer você escolher, o maior aumento de desempenho que você verá está passando do acesso a arquivos sem buffer para buffer. Ajustar o tamanho do buffer pode melhorar um pouco o desempenho, mas, a menos que você esteja usando um tamanho de buffer extremamente pequeno ou muito grande, é improvável que tenha um impacto significativo.

Adrian Krebs
fonte