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;
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