6 Eylül 2023 Çarşamba

Java Microbenchmark Harness (JMH) BufferedInputStream Test

Giriş
Elimizde BufferedInputStream işlevini sağlayan 3 tane sınıf vardı. Bunlardan 
- İki tanesi - yani Apache Common IO sınıfı ve Hazelcast'in kendi sınıfı - unsynchronized çalışıyor. 
- Bir tanesiyse - yani JDK ile gelen sınıf - synchronized çalışıyor. 

Merak ettiğimiz şey single threaded ortamda synchronized sınıfın read() metodunun maliyetinin çok fark yaratıp yaratmadığıydı

Not : JMH ile ölçüm yapan GitHub projesi burada

Sınıflar şöyle
1. java.io.BufferedInputStream;
3. org.apache.commons.io.input.UnsynchronizedBufferedInputStream;
Test kodu şöyle 
1. InputStream'den 100, 1000 ve 10_000 byte büyüklüğünde farklı bellek büyüklükleri ile okuma yappıyoruz
2. Girdi olarak kullanılan dosyada 30K satır var
// Only one forked process
@Fork(value = 1)
// Only one thread
@Threads(1)
// use the same instance of this class for the whole benchmark,
// so it is OK to have some member variables
@State(Scope.Benchmark)
// calculate the average time of one call
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
// 5 iterations to warm-up, that may last 20 milliseconds each
@Warmup(iterations = 5, time = 20, timeUnit = TimeUnit.MILLISECONDS)
// 20 iterations to measure, that may last 200 milliseconds each
@Measurement(iterations = 20, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class BufferedInputStreamJmh {
  @Param({"100", "1000", "10000"})
  private int readBufferSize;

  private BufferedInputStream bufferedInputStream;
  private BufferingInputStream bufferingInputStream;
  private UnsynchronizedBufferedInputStream unsynchronizedBufferedInputStream;

  public static void main(String[] args) throws Exception {
    org.openjdk.jmh.Main.main(args);
  }
 
  private int countNewLinesManually(InputStream inputStream, int customBytesToBuffer) throws IOException {
    byte[] buff = new byte[customBytesToBuffer];
    int count = 1;
    int bytesRead;
    while ((bytesRead = inputStream.read(buff)) != -1) {
      for (int i = 0; i < bytesRead; i++) {
        if (buff[i] == '\n') {
          count++;
        }
      }
    }
    return count;
  }
...
}
@Setup ve @Teardown içinde her bir Invocation için yeni bir stream yaratıyoruz. Amaç sadece read() metodunun synchronized olup olmaması neyi değiştiriyor onu ölçmek. Bu yüzden test içine stream yaratma maliyetini dahil etmek istemedik. InputStream için 65536 byte büyüklüğünde oldukça büyük bir buffer kullanıyoruz
@Setup(Level.Invocation)
public void setup() throws IOException {
  final int streamInternalBufferSize = 1 << 16;
  bufferedInputStream = new BufferedInputStream( new FileInputStream("myfile.txt"), streamInternalBufferSize);
  bufferingInputStream = new BufferingInputStream( new FileInputStream("myfile.txt"), streamInternalBufferSize);

  unsynchronizedBufferedInputStream = new UnsynchronizedBufferedInputStream.Builder()
    .setInputStream(new FileInputStream("myfile.txt"))
    .setBufferSize(streamInternalBufferSize)
    .get();
}

@TearDown(Level.Invocation)
public void teardown() throws IOException {
  bufferedInputStream.close();
  bufferingInputStream.close();
  unsynchronizedBufferedInputStream.close();
}
Testler şöyle
@Benchmark
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public int useBufferingInputStream() throws IOException {
  return countNewLinesManually(bufferingInputStream, readBufferSize);
  //assertThat(numberOfLines).isEqualTo(30_000);
}

@Benchmark
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public int useBufferedInputStream() throws IOException {
  return countNewLinesManually(bufferedInputStream, readBufferSize);
  //assertThat(numberOfLines).isEqualTo(30_000);
}

@Benchmark
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public int useUnsynchronizedBufferedInputStream() throws IOException {
  return countNewLinesManually(unsynchronizedBufferedInputStream, readBufferSize);
  //assertThat(numberOfLines).isEqualTo(30_000);
}
Test sonuçları şöyle. 

- Java 11'de hem MacOS hem de Windows 11'de BufferingInputStream daha iyi sonuç verdi. 
- JDK BufferednputStream ikinci. 
UnsynchronizedBufferedInputStream sınıfı unsynchronized kod kullanmasına rağmen üçüncü sırada. Üstelik UnsynchronizedBufferedInputStream sınıfı 100 byte okumada çok kötü sonuç veriyor.
Java 11 MacOs

Benchmark                                                    (readBufferSize)  Mode  Cnt  Score   Error  Units
BufferedInputStreamJmh.useBufferedInputStream                             100  avgt   20  0.276 ± 0.003  ms/op
BufferedInputStreamJmh.useBufferedInputStream                            1000  avgt   20  0.229 ± 0.003  ms/op
BufferedInputStreamJmh.useBufferedInputStream                           10000  avgt   20  0.236 ± 0.003  ms/op
BufferedInputStreamJmh.useBufferingInputStream                            100  avgt   20  0.268 ± 0.004  ms/op
BufferedInputStreamJmh.useBufferingInputStream                           1000  avgt   20  0.226 ± 0.001  ms/op
BufferedInputStreamJmh.useBufferingInputStream                          10000  avgt   20  0.227 ± 0.002  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream               100  avgt   20  4.163 ± 0.018  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream              1000  avgt   20  0.594 ± 0.002  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream             10000  avgt   20  0.240 ± 0.001  ms/op

Java 11 Windows

Benchmark                                                    (readBufferSize)  Mode  Cnt  Score   Error  Units
BufferedInputStreamJmh.useBufferedInputStream                             100  avgt   20  0,336 ± 0,009  ms/op
BufferedInputStreamJmh.useBufferedInputStream                            1000  avgt   20  0,267 ± 0,009  ms/op
BufferedInputStreamJmh.useBufferedInputStream                           10000  avgt   20  0,264 ± 0,014  ms/op
BufferedInputStreamJmh.useBufferingInputStream                            100  avgt   20  0,283 ± 0,008  ms/op
BufferedInputStreamJmh.useBufferingInputStream                           1000  avgt   20  0,245 ± 0,006  ms/op
BufferedInputStreamJmh.useBufferingInputStream                          10000  avgt   20  0,234 ± 0,008  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream               100  avgt   20  8,785 ± 0,135  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream              1000  avgt   20  1,076 ± 0,016  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream             10000  avgt   20  0,308 ± 0,014  ms/op
Java 17'de sonuç aynı. JDK 15 ile artık biased locking artık yok. Yani kodda synchronized varsa bile artık hep etkin.  Buna rağmen BufferedInputStream etkilenmiyor
Java 17 MacOs

Benchmark                                                    (readBufferSize)  Mode  Cnt  Score   Error  Units
BufferedInputStreamJmh.useBufferedInputStream                             100  avgt   20  0.277 ± 0.001  ms/op
BufferedInputStreamJmh.useBufferedInputStream                            1000  avgt   20  0.224 ± 0.001  ms/op
BufferedInputStreamJmh.useBufferedInputStream                           10000  avgt   20  0.231 ± 0.004  ms/op
BufferedInputStreamJmh.useBufferingInputStream                            100  avgt   20  0.268 ± 0.002  ms/op
BufferedInputStreamJmh.useBufferingInputStream                           1000  avgt   20  0.216 ± 0.001  ms/op
BufferedInputStreamJmh.useBufferingInputStream                          10000  avgt   20  0.223 ± 0.003  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream               100  avgt   20  3.582 ± 0.008  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream              1000  avgt   20  0.536 ± 0.005  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream             10000  avgt   20  0.231 ± 0.003  ms/op

Java 17 Windows

Benchmark                                                    (readBufferSize)  Mode  Cnt  Score   Error  Units
BufferedInputStreamJmh.useBufferedInputStream                             100  avgt   20  0,422 ± 0,012  ms/op
BufferedInputStreamJmh.useBufferedInputStream                            1000  avgt   20  0,302 ± 0,008  ms/op
BufferedInputStreamJmh.useBufferedInputStream                           10000  avgt   20  0,256 ± 0,008  ms/op
BufferedInputStreamJmh.useBufferingInputStream                            100  avgt   20  0,299 ± 0,004  ms/op
BufferedInputStreamJmh.useBufferingInputStream                           1000  avgt   20  0,273 ± 0,004  ms/op
BufferedInputStreamJmh.useBufferingInputStream                          10000  avgt   20  0,237 ± 0,005  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream               100  avgt   20  8,624 ± 0,184  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream              1000  avgt   20  1,076 ± 0,041  ms/op
BufferedInputStreamJmh.useUnsynchronizedBufferedInputStream             10000  avgt   20  0,300 ± 0,006  ms/op


Hiç yorum yok:

Yorum Gönder