2012-01-03 1 views
37

J'ai un module qui est responsable de la lecture, le traitement et l'écriture des octets sur le disque. Les octets arrivent sur UDP et, après l'assemblage des datagrammes individuels, le tableau d'octets final qui est traité et écrit sur le disque est généralement compris entre 200 octets et 500 000 octets. Occasionnellement, il y aura des tableaux d'octets qui, après assemblage, sont plus de 500 000 octets, mais ceux-ci sont relativement rares. J'utilise actuellement le FileOutputStream de write(byte\[\]) method. J'expérimente également avec l'emballage du FileOutputStream dans un BufferedOutputStream, y compris en utilisant the constructor that accepts a buffer size as a parameter.À quel point l'encapsulation d'un FileOutputStream avec un BufferedOutputStream est-elle logique en termes de performances?

Il semble que l'utilisation du BufferedOutputStream tend vers une performance légèrement meilleure, mais j'ai juste commencé à expérimenter avec différentes tailles de tampons. Je n'ai qu'un nombre limité de données d'exemple avec lesquelles travailler (deux ensembles de données provenant d'échantillons que je peux faire passer dans mon application). Y a-t-il une règle générale que je pourrais appliquer pour essayer de calculer la taille optimale des tampons afin de réduire les écritures sur disque et maximiser les performances de l'écriture sur disque, compte tenu des informations que je connais sur les données que j'écris?

Répondre

28

BufferedOutputStream aide lorsque les écritures sont plus petites que la taille de la mémoire tampon, par ex. 8 Ko. Pour les plus grandes écritures, cela n'aide pas et ne le rend pas pire. Si TOUTES vos écritures sont plus grandes que la taille de la mémoire tampon ou si vous videz toujours() après chaque écriture, je n'utiliserais pas de tampon. Cependant, si une bonne partie de vos écritures est inférieure à la taille de la mémoire tampon et que vous n'utilisez pas flush() à chaque fois, cela vaut la peine d'en avoir.

Vous pouvez trouver une augmentation de la taille de la mémoire tampon à 32 Ko ou plus qui vous apporte une amélioration marginale, ou l'aggraver. YMMV


Vous pourriez trouver le code pour BufferedOutputStream.write utile

/** 
* Writes <code>len</code> bytes from the specified byte array 
* starting at offset <code>off</code> to this buffered output stream. 
* 
* <p> Ordinarily this method stores bytes from the given array into this 
* stream's buffer, flushing the buffer to the underlying output stream as 
* needed. If the requested length is at least as large as this stream's 
* buffer, however, then this method will flush the buffer and write the 
* bytes directly to the underlying output stream. Thus redundant 
* <code>BufferedOutputStream</code>s will not copy data unnecessarily. 
* 
* @param  b  the data. 
* @param  off the start offset in the data. 
* @param  len the number of bytes to write. 
* @exception IOException if an I/O error occurs. 
*/ 
public synchronized void write(byte b[], int off, int len) throws IOException { 
    if (len >= buf.length) { 
     /* If the request length exceeds the size of the output buffer, 
      flush the output buffer and then write the data directly. 
      In this way buffered streams will cascade harmlessly. */ 
     flushBuffer(); 
     out.write(b, off, len); 
     return; 
    } 
    if (len > buf.length - count) { 
     flushBuffer(); 
    } 
    System.arraycopy(b, off, buf, count, len); 
    count += len; 
} 
+0

Quelque chose que je ne l'ai pas encore trouvé - quelle est la taille de la mémoire tampon par défaut du BufferedOutputStream en Java 6? Vous parlez 8 Ko - est-ce la valeur par défaut en Java? Les Javadocs pour 1.4.2 disent que le tampon est de 512 octets, ce qui signifie que la plupart de ce que j'écris a tendance à tomber entre 200 et 400 octets par tableau. Toutefois, cette information est supprimée de la documentation Java 6. –

+3

@Thomas - [regardant le code source] (http://www.docjar.com/html/api/java/io/BufferedOutputStream.java.html#51), la taille par défaut est 8192. Je suppose qu'ils supprimé la spécification de taille par défaut pour pouvoir la changer quand un nouveau "défaut le plus sensible" apparaît. Si la taille d'un tampon est importante, vous voudrez probablement le spécifier explicitement. – gustafc

+1

@gustafc Merci. J'oublie toujours que je peux regarder le code source Java. –

1

Je dernièrement essayé d'explorer les performances IO. De ce que j'ai observé, l'écriture directe à un FileOutputStream a conduit à de meilleurs résultats; que j'ai attribué à l'appel natif de FileOutputStream pour write(byte[], int, int). De plus, j'ai également observé que lorsque la latence de BufferedOutputStream commence à converger vers celle de direct FileOutputStream, elle fluctue beaucoup plus, c'est-à-dire qu'elle peut même brusquement doubler (je n'ai pas encore réussi à savoir pourquoi).

P.S. J'utilise Java 8 et je ne serai pas en mesure de commenter maintenant si mes observations seront valables pour les versions précédentes de Java.

est ici le code je l'ai testé, où mon entrée était un fichier ~ 10KB

public class WriteCombinationsOutputStreamComparison { 
    private static final Logger LOG = LogManager.getLogger(WriteCombinationsOutputStreamComparison.class); 

public static void main(String[] args) throws IOException { 

    final BufferedInputStream input = new BufferedInputStream(new FileInputStream("src/main/resources/inputStream1.txt"), 4*1024); 
    final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
    int data = input.read(); 
    while (data != -1) { 
     byteArrayOutputStream.write(data); // everything comes in memory 
     data = input.read(); 
    } 
    final byte[] bytesRead = byteArrayOutputStream.toByteArray(); 
    input.close(); 

    /* 
    * 1. WRITE USING A STREAM DIRECTLY with entire byte array --> FileOutputStream directly uses a native call and writes 
    */ 
    try (OutputStream outputStream = new FileOutputStream("src/main/resources/outputStream1.txt")) { 
     final long begin = System.nanoTime(); 
     outputStream.write(bytesRead); 
     outputStream.flush(); 
     final long end = System.nanoTime(); 
     LOG.info("Total time taken for file write, writing entire array [nanos=" + (end - begin) + "], [bytesWritten=" + bytesRead.length + "]"); 
     if (LOG.isDebugEnabled()) { 
      LOG.debug("File reading result was: \n" + new String(bytesRead, Charset.forName("UTF-8"))); 
     } 
    } 

    /* 
    * 2. WRITE USING A BUFFERED STREAM, write entire array 
    */ 

    // changed the buffer size to different combinations --> write latency fluctuates a lot for same buffer size over multiple runs 
    try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("src/main/resources/outputStream1.txt"), 16*1024)) { 
     final long begin = System.nanoTime(); 
     outputStream.write(bytesRead); 
     outputStream.flush(); 
     final long end = System.nanoTime(); 
     LOG.info("Total time taken for buffered file write, writing entire array [nanos=" + (end - begin) + "], [bytesWritten=" + bytesRead.length + "]"); 
     if (LOG.isDebugEnabled()) { 
      LOG.debug("File reading result was: \n" + new String(bytesRead, Charset.forName("UTF-8"))); 
     } 
    } 
} 
} 

SORTIE:

2017-01-30 23:38:59.064 [INFO] [main] [WriteCombinationsOutputStream] - Total time taken for file write, writing entire array [nanos=100990], [bytesWritten=11059] 

2017-01-30 23:38:59.086 [INFO] [main] [WriteCombinationsOutputStream] - Total time taken for buffered file write, writing entire array [nanos=142454], [bytesWritten=11059] 
+0

J'ai effectué des tests similaires et je peux confirmer que l'utilisation d'un BufferedOutputStream rend l'écriture de fichiers plus rapide mais plus lente, probablement parce que les données écrites sont déjà mises en cache sur plusieurs niveaux. moyen. –

+0

@GOTO Merci de votre confirmation. Y a-t-il des ressources que vous connaissez peut-être, qui peuvent m'aider à approfondir la façon dont fonctionnent les entrées-sorties et les caches internes? –

+0

Pas vraiment. Si cela aide à googler, les composants de mise en cache de fichiers sont appelés Gestionnaire de cache dans Windows et Cache de page sous Linux. Les disques durs et autres périphériques de stockage viennent également avec différentes sortes de caches d'E/S (bien que les bases soient probablement les mêmes). –

Questions connexes