08 stycznia 2024 blog java jmh io
JMH jest przyjemnym frameworkiem do testowania wydajności. Po dodaniu zależności do pom.xml
wystarczy dodanie kilku adnotacji, żeby przetestować jakiś fragment kodu.
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class FileReaderBenchmark {
@Setup
public void setup() {
}
@TearDown
public void tearDown() {
}
@Benchmark
public void ,ethod() {
}
}
W Javie istnieje kilka sposobów odczytu plików, najbardziej popularny to chyba new BufferedInputStream(new FileInputStream(new File(fileName)))
.
Też szukając informacji natknąłem się na Tuning Java I/O Performance gdzie między innymi wykorzystano new RandomAccessFile(fileName, "r")
.
Ostatnią metodą, ale moim zdaniem najciekawszą jest new RandomAccessFile(fileName, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize)
. Dużo artykułów opisujących memory mapped file odczytuje bajty pojedynczo, ale o wiele wydajniej jest, jeśli czytamy dane w większych porcjach.
Przetestujmy wydajność tych 3 metod przy pomocy JMH.
@Benchmark
public long bufferedInputStream(Blackhole blackhole) throws Exception {
byte[] buffer = new byte[1024 * 16];
int k;
try (UsingBufferedInputStream reader = new UsingBufferedInputStream(filename)) {
while ((k = reader.read(buffer, 0, buffer.length)) > 0) {
blackhole.consume(k);
blackhole.consume(buffer);
}
}
return 0;
}
@Benchmark
public long readRandom(Blackhole blackhole) throws Exception {
try (UsingReadRandom reader = new UsingReadRandom(filename)) {
long pos = 0;
int c;
byte buf[] = new byte[1];
while ((c = reader.read(pos)) != -1) {
pos++;
buf[0] = (byte) c;
blackhole.consume(c);
blackhole.consume(buf);
}
}
return 0;
}
@Benchmark
public long bufferedMemory(Blackhole blackhole) throws Exception {
byte[] buffer = new byte[1024 * 16];
int k;
try (UsingBufferedMemoryMappedFile reader = new UsingBufferedMemoryMappedFile(filename)) {
while ((k = reader.read(buffer, 0, buffer.length)) > 0) {
blackhole.consume(k);
blackhole.consume(buffer);
}
}
return 0;
}
Cały kod benchmarku.
Benchmark Mode Cnt Score Error Units
FileReaderBenchmark.bufferedInputStream avgt 5 0.142 ± 0.010 s/op
FileReaderBenchmark.bufferedMemory avgt 5 0.093 ± 0.005 s/op
FileReaderBenchmark.readRandom avgt 5 1.987 ± 0.637 s/op
Na moim systemie odczyt 1GB pliku przy użyciu memory mapped file średnio trwał 93 milisekundy. Połowę wolniejszy był BufferedInputStream
.
Dla porównania dd if=/dev/urandom of=1G bs=1M count=1000; hyperfine "dd if=1G bs=16K of=/dev/null"
daje wynik:
Benchmark 1: dd if=1G bs=16K of=/dev/null
Time (mean ± σ): 105.1 ms ± 8.9 ms [User: 4.4 ms, System: 101.2 ms]
Range (min … max): 98.6 ms … 134.6 ms 21 runs
Nie tak źle Java, nie tak źle.