web-dev-qa-db-fra.com

À quel moment le fait d'envelopper un FileOutputStream avec un BufferedOutputStream est-il logique, en termes de performances?

J'ai un module qui est responsable de la lecture, du traitement et de l'écriture d'octets sur le disque. Les octets arrivent via UDP et, une fois les datagrammes individuels assemblés, le tableau d'octets final qui est traité et écrit sur le disque se situe généralement entre 200 et 500 000 octets. Parfois, il y aura des tableaux d'octets qui, après l'assemblage, dépasseront 500 000 octets, mais ils sont relativement rares.

J'utilise actuellement la méthode FileOutputStreamwrite(byte\[\]) . J'expérimente également avec le wrapper du FileOutputStream dans un BufferedOutputStream , y compris en utilisant le constructeur qui accepte une taille de tampon comme paramètre .

Il semble que l'utilisation de BufferedOutputStream tend vers des performances légèrement meilleures, mais je viens juste de commencer à expérimenter différentes tailles de tampon. Je n'ai qu'un ensemble limité d'exemples de données à utiliser (deux ensembles de données d'exécutions que je peux diriger via mon application). Existe-t-il une règle générale que je pourrais appliquer pour essayer de calculer les tailles de mémoire tampon optimales pour réduire les écritures sur le disque et maximiser les performances d'écriture sur le disque compte tenu des informations que je connais sur les données que j'écris?

44
Thomas Owens

BufferedOutputStream aide lorsque les écritures sont plus petites que la taille du tampon, par ex. 8 Ko. Pour les écritures plus volumineuses, cela n'aide pas et ne fait pas empirer les choses. Si TOUTES vos écritures sont plus grandes que la taille du 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 du tampon et que vous n'utilisez pas flush () à chaque fois, cela vaut la peine.

L'augmentation de la taille du tampon à 32 Ko ou plus peut vous apporter une amélioration marginale ou l'aggraver. YMMV


Vous pourriez trouver le code de 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;
}
32
Peter Lawrey

J'ai récemment essayé d'explorer les performances de IO. D'après ce que j'ai observé, écrire directement dans un FileOutputStream a conduit à de meilleurs résultats; que j'ai attribués à 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 brusquement même double-up (je n'ai pas encore pu savoir pourquoi).

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

Voici le code que j'ai testé, où mon entrée était un fichier de ~ 10 Ko

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")));
        }
    }
}
}

PRODUCTION:

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]
1
Dev Amitabh