Puffergröße für I/O ermitteln

Wenn man Daten in eine Datei schreibt tut man dies am besten gepuffert den sonst wird im ungünstigsten Fall für jedes geschriebene byte eine Schreiboperation ausgelöst. Im Puffer werden bytes die in eine Datei geschrieben werden sollen gesammelt und dann in Summe auf die Festplatte geschrieben. Doch auch die Puffergröße hat noch einmal erheblichen Einfluss auf die Schreibperformance der Applikation. Um den optimalen Wert zu ermitteln gibt er hier ein kleines Java Programm das einen Benchmark durchführt um die optimale Puffergröße zu ermitteln.

Die optimale Puffergröße hängt von Faktoren wie Blockgröße der formatierten Festplatte, Caching Algorithmus des Betriebsystems und Caches der Festplatte oder Storages ab. Deswegen ist es mitunter wichtig die Puffergröße individuell anzupassen um optimale Performance zu gewährleisten. Dafür habe ich ein kleines Java Programm geschrieben das versucht die optimale Puffergröße zu ermitteln.

Das Programm benötigt dafür als Parameter ein Verzeichnis in dem es den Test durchführen kann. Die Größe für eine geschriebene Datei und die Anzahl der Durchläufe und kann zum Beispiel so benutzt werden.

java -jar bsb.jar /tmp/bsb-benchmark 2048 10

Die ausführbare JAR Datei gibt es zum Download auf Github hier in diesem Repository. Der Vollständigkeit halber gibt es hier auch den Quellcode.

Nachtrag: Im Regelfall sollte der Test als optimale Puffergröße mit der Blocksize mit der Block Size des Dateisystems übereinstimmen und kann somit den Benchmark ersparen. Diese kann auf Linux und Mac OS Betriebsystemen mit dem stat oder tune2fs Befehl ermittelt werden. Ich verwende den stat Befehl da dieser auch bei nicht Partitionen die unmountet sind funktioniert. Zum Beispiel:

stat -f /dev/sda1

import java.net.URL;
import java.io.IOException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.BufferedWriter;
import java.io.FileWriter;

/**
 * Buffer Size Benchmark
 *
 * This application creates random string bunches of 2 KB and writes them to
 * files on the given path. It increases the buffer to determine optimum buffer
 * size.
 *
 * @author Daniel Kröger <daniel.kroeger@kofrezo.io>
 * @version 30 Aug. 2015
 */
public class BufferSizeBenchmark
{
  public void help() {
    System.out.println("usage: java buffer_size_benchmark [directory] [size] [runs]");
    System.out.println("  directory:  absolute path where files for benchmark should be written to");
    System.out.println("  size     :  size of one file to write in KB (max. 10240)");
    System.out.println("  runs     :  number of runs to perform - the more the better(max. " + Integer.MAX_VALUE + ")");
  }

  public String getRandom(int size) {
    try {
      int lastPercent = 0;
      size = size * 1000;
      byte[] buffer = new byte[1024];
      StringBuilder random = new StringBuilder();
      URL url = new URL("http://loripsum.net/api/" + 5000 + "/verylong");
      BufferedInputStream bis = new BufferedInputStream(url.openStream());

      System.out.print("  generating random data: ");
      while (random.length() < size) {
        while (bis.available() > 0) {
          int read = bis.read(buffer, 0, 1024);
          if (read > -1) {
            for (int i = 0; i < read; i++) {
              if (random.length() < size) {
                random.append((char)buffer[i]);
              }
            }
          }

          int percent = (int)((double)random.length() / (double)size * 100.0);
          if (percent > lastPercent && percent % 10 == 0) {
            System.out.print(percent + "% ");
            lastPercent = percent;
          }
        }
      }
      System.out.print(System.getProperty("line.separator"));

      bis.close();
      return random.toString();
    }
    catch(IOException ex) {
      ex.printStackTrace();
    }

    return null;
  }

  public void write(String filename, String content, int bufferSize) {
    try {
      File file = new File(filename);
      if (!file.exists()) {
        file.createNewFile();
      }

      int position = 0;
      char[] buffer = new char[bufferSize];
      BufferedWriter bw = new BufferedWriter(new FileWriter(file));
      while(position < content.length()) {
        if (position + bufferSize < content.length()) {
          content.getChars(position, position + bufferSize, buffer, 0);
          bw.write(buffer, 0, bufferSize);
          bw.flush();
        }
        else {
          int left = content.length() - position;
          content.getChars(position, content.length(), buffer, 0);
          bw.write(buffer, 0, left);
          bw.flush();
        }
        position += bufferSize;
      }

      bw.close();
    }
    catch(IOException ex) {
      ex.printStackTrace();
    }
  }

  public void stats(int[] stats) {
    for (int i = 0; i < stats.length; i++) {
      System.out.println("optimum buffer size in run " + (i + 1) + ":\t" + stats[i]);
    }
    System.out.println("");
  }

  public static void main(String[] args)
  {
    try {
      BufferSizeBenchmark bsb = new BufferSizeBenchmark();

      if (args.length != 3) {
        bsb.help();
        System.exit(0);
      }

      File directory = new File(args[0]);
      int size = Integer.parseInt(args[1]);
      int runs = Integer.parseInt(args[2]);

      if (!directory.exists()) {
        directory.mkdir();
      }

      if (size < 1 || size > 10240) {
        System.out.println("unallowed value for size using 1024");
        size = 1;
      }

      if (runs < 1 || runs > Integer.MAX_VALUE) {
        System.out.println("unallowed value for runs using 10");
        runs = 10;
      }

      int[] stats = new int[runs];
      for (int i = 1; i <= runs; i++) {
        System.out.println("run " + i + "/" + runs);

        int buffersize = 2;
        long lastrun = Long.MAX_VALUE;
        while (true) {
          String filename = directory.getCanonicalPath() + "/buffer_size_benchmark";
          filename += "-run_" + i;
          filename += "-size_" + size + "kb";
          filename += "-buffer_size_" + buffersize + "bytes";
          filename += ".txt";

          String content = bsb.getRandom(size);

          long start = System.currentTimeMillis();
          bsb.write(filename, content, buffersize);
          long currentrun = System.currentTimeMillis() - start;

          if (lastrun > currentrun) {
            lastrun = currentrun;
            buffersize = buffersize * 2;
            System.out.println("  took " + currentrun + " ms to write " + size + " kb with buffer size of " + buffersize + " bytes");
          }
          else {
            System.out.println("  took " + currentrun + " ms to write " + size + " kb with buffer size of " + buffersize + " bytes");
            System.out.println("    => optimum buffer size is " + buffersize + " bytes");
            stats[i - 1] = buffersize;
            break;
          }
        }

        System.out.println("");
      }

      bsb.stats(stats);
    }
    catch(IOException ex) {
      ex.printStackTrace();
    }
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *