Comment déterminez-vous la taille de tampon idéale lors de l'utilisation de FileInputStream?

156

J'ai une méthode qui crée un MessageDigest (un hachage) à partir d'un fichier, et je dois le faire à beaucoup de fichiers (> = 100 000). Quelle est la taille du tampon utilisé pour lire les fichiers pour maximiser les performances?

Presque tout le monde connaît le code de base (que je vais répéter ici au cas 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();

Quelle est la taille idéale de la mémoire tampon pour maximiser le débit? Je sais que cela dépend du système, et je suis à peu près sûr que son système d'exploitation, son système de fichiers et son disque dur dépendent, et il peut y avoir d'autres matériels / logiciels dans le mélange.

(Je dois souligner que je suis un peu nouveau dans Java, donc cela peut être juste un appel d'API Java que je ne connais pas.)

Edit: Je ne sais pas à l'avance sur les types de systèmes sur lesquels cela sera utilisé, donc je ne peux pas en supposer beaucoup. (J'utilise Java pour cette raison.)

Edit: Le code ci-dessus manque des choses comme try..catch pour réduire la taille de l'article

ARKBAN
la source

Réponses:

213

La taille optimale du tampon est liée à un certain nombre de choses: la taille du bloc du système de fichiers, la taille du cache du processeur et la latence du cache.

La plupart des systèmes de fichiers sont configurés pour utiliser des tailles de bloc de 4096 ou 8192. En théorie, si vous configurez la taille de votre tampon de manière à lire quelques octets de plus que le bloc de disque, les opérations avec le système de fichiers peuvent être extrêmement inefficaces (c'est-à-dire si vous configuré votre tampon pour lire 4100 octets à la fois, chaque lecture nécessiterait 2 lectures de bloc par le système de fichiers). Si les blocs sont déjà dans le cache, vous finissez par payer le prix de la RAM -> latence du cache L3 / L2. Si vous n'êtes pas chanceux et que les blocs ne sont pas encore en cache, vous payez également le prix de la latence disque-> RAM.

C'est pourquoi vous voyez la plupart des tampons dimensionnés comme une puissance de 2, et généralement supérieure (ou égale) à la taille du bloc de disque. Cela signifie qu'une de vos lectures de flux peut entraîner plusieurs lectures de bloc de disque - mais ces lectures utiliseront toujours un bloc complet - pas de lectures gaspillées.

Maintenant, cela est un peu décalé dans un scénario de streaming typique car le bloc qui est lu à partir du disque sera toujours en mémoire lorsque vous frappez la prochaine lecture (nous faisons des lectures séquentielles ici, après tout) - donc vous vous retrouvez payer la RAM -> prix de latence du cache L3 / L2 à la prochaine lecture, mais pas la latence disque-> RAM. En termes d'ordre de grandeur, la latence disque-> RAM est si lente qu'elle comble à peu près toute autre latence que vous pourriez avoir à gérer.

Donc, je soupçonne que si vous exécutez un test avec différentes tailles de cache (je ne l'ai pas fait moi-même), vous constaterez probablement un impact important de la taille du cache jusqu'à la taille du bloc du système de fichiers. Au-dessus de cela, je soupçonne que les choses se stabiliseraient assez rapidement.

Il y a une tonne de conditions et d'exceptions ici - les complexités du système sont en fait assez stupéfiantes (le simple fait de maîtriser les transferts de cache L3 -> L2 est incroyablement complexe, et cela change avec chaque type de processeur).

Cela conduit à la réponse du `` monde réel '': si votre application est à 99%, définissez la taille du cache sur 8192 et continuez (mieux encore, choisissez l'encapsulation plutôt que les performances et utilisez BufferedInputStream pour masquer les détails). Si vous êtes dans le 1% des applications qui dépendent fortement du débit du disque, concevez votre implémentation afin de pouvoir échanger différentes stratégies d'interaction de disque, et fournissez les boutons et les molettes pour permettre à vos utilisateurs de tester et d'optimiser (ou d'en proposer système d'auto-optimisation).

Kevin Day
la source
3
J'ai fait du banchmarking sur un téléphone mobile (Nexus 5X) pour mon application Android pour les deux: petits fichiers (3,5 Mo) et gros fichiers (175 Mo). Et j'ai découvert que la taille dorée serait l'octet [] de 524288 longueurs. Eh bien, vous pouvez gagner 10 à 20 ms si vous basculez entre une petite mémoire tampon de 4 Ko et une grande mémoire tampon de 524 Ko en fonction de la taille du fichier, mais cela n'en vaut pas la peine. Donc 524 Kb était la meilleure option dans mon cas.
Kirill Karmazin
19

Oui, cela dépend probablement de diverses choses - mais je doute que cela fasse une grande différence. J'ai tendance à opter pour 16K ou 32K comme un bon équilibre entre l'utilisation de la mémoire et les performances.

Notez que vous devriez avoir un bloc try / finally dans le code pour vous assurer que le flux est fermé même si une exception est levée.

Jon Skeet
la source
J'ai édité le post sur le try..catch. Dans mon vrai code, j'en ai un, mais je l'ai laissé de côté pour raccourcir le message.
ARKBAN
1
si nous voulons lui définir une taille fixe, quelle taille est la meilleure? 4k, 16k ou 32k?
BattleTested
2
@MohammadrezaPanahi: Veuillez ne pas utiliser de commentaires pour les utilisateurs de blaireau. Vous avez attendu moins d'une heure avant un deuxième commentaire. N'oubliez pas que les utilisateurs peuvent facilement être endormis, ou en réunion, ou essentiellement occupés par d'autres choses et n'ont aucune obligation de répondre aux commentaires. Mais pour répondre à votre question: cela dépend entièrement du contexte. Si vous exécutez sur un système très contraint en mémoire, vous voulez probablement un petit tampon. Si vous utilisez un grand système, l'utilisation d'un tampon plus grand réduira le nombre d'appels de lecture. La réponse de Kevin Day est très bonne.
Jon Skeet
7

Dans la plupart des cas, cela n'a pas vraiment d'importance. Choisissez simplement une bonne taille telle que 4K ou 16K et respectez-la. Si vous êtes certain qu'il s'agit du goulot d'étranglement dans votre application, vous devez commencer le profilage pour trouver la taille de tampon optimale. Si vous choisissez une taille trop petite, vous perdrez du temps à effectuer des opérations d'E / S supplémentaires et des appels de fonctions supplémentaires. Si vous choisissez une taille trop grande, vous commencerez à voir beaucoup d'erreurs de cache qui vous ralentiront vraiment. N'utilisez pas de tampon plus grand que la taille de votre cache L2.

Adam Rosenfield
la source
4

Dans le cas idéal, nous devrions avoir suffisamment de mémoire pour lire le fichier en une seule opération de lecture. Ce serait le plus performant car nous laissons le système gérer le système de fichiers, les unités d'allocation et le disque dur à volonté. En pratique, vous avez la chance de connaître la taille des fichiers à l'avance, il suffit d'utiliser la taille moyenne des fichiers arrondie à 4K (unité d'allocation par défaut sur NTFS). Et le meilleur de tous: créez un benchmark pour tester plusieurs options.

Ovidiu Pacurar
la source
voulez-vous dire que la meilleure taille de tampon pour la lecture et l'écriture dans un fichier est de 4k?
BattleTested
4

Vous pouvez utiliser les BufferedStreams / lecteurs, puis utiliser leurs tailles de tampon.

Je crois que les BufferedXStreams utilisent 8192 comme taille de tampon, mais comme Ovidiu l'a dit, vous devriez probablement exécuter un test sur tout un tas d'options. Cela dépendra vraiment du système de fichiers et des configurations de disque pour savoir quelles sont les meilleures tailles.

John Gardner
la source
4

La lecture de fichiers à l'aide de FileChannel et de MappedByteBuffer de Java NIO aboutira très probablement à une solution qui sera beaucoup plus rapide que toute solution impliquant FileInputStream. Fondamentalement, mappez en mémoire les gros fichiers et utilisez des tampons directs pour les petits.

Alexandre
la source
4

Dans la source de BufferedInputStream, vous trouverez: private static int DEFAULT_BUFFER_SIZE = 8192;
Vous pouvez donc utiliser cette valeur par défaut.
Mais si vous pouvez trouver plus d'informations, vous obtiendrez des réponses plus valables.
Par exemple, votre adsl peut préférer un tampon de 1454 octets, car la charge utile de TCP / IP. Pour les disques, vous pouvez utiliser une valeur correspondant à la taille de bloc de votre disque.

GoForce5500
la source
1

Comme déjà mentionné dans d'autres réponses, utilisez BufferedInputStreams.

Après cela, je suppose que la taille du tampon n'a pas vraiment d'importance. Soit le programme est lié aux E / S, et l'augmentation de la taille de la mémoire tampon par rapport à la valeur par défaut BIS n'aura aucun impact important sur les performances.

Ou le programme est lié au processeur dans MessageDigest.update (), et la majorité du temps n'est pas passée dans le code de l'application, donc le peaufiner n'aidera pas.

(Hmm ... avec plusieurs cœurs, les threads pourraient aider.)

Maglob
la source
0

1024 convient à une grande variété de circonstances, bien qu'en pratique, vous puissiez constater de meilleures performances avec une taille de mémoire tampon plus ou moins grande.

Cela dépendrait d'un certain nombre de facteurs, notamment la taille des blocs du système de fichiers et le matériel du processeur.

Il est également courant de choisir une puissance de 2 pour la taille de la mémoire tampon, car la plupart du matériel sous-jacent est structuré avec des blocs de fichiers et des tailles de cache d'une puissance de 2. Les classes Buffered vous permettent de spécifier la taille de la mémoire tampon dans le constructeur. Si aucun n'est fourni, ils utilisent une valeur par défaut, qui est une puissance de 2 dans la plupart des JVM.

Quelle que soit la taille de la mémoire tampon que vous choisissez, la plus grande augmentation des performances que vous constaterez est le passage d'un accès aux fichiers sans tampon à un accès aux fichiers tamponnés. L'ajustement de la taille de la mémoire tampon peut améliorer légèrement les performances, mais à moins que vous n'utilisiez une taille de mémoire tampon extrêmement petite ou extrêmement grande, il est peu probable qu'il ait un impact significatif.

Adrian Krebs
la source