Java Microbenchmark Harness + memory mapped file
08 stycznia 2024 blog java jmh io
Jak najwydajniej odczytać plik?
Java Microbenchmark Harness + memory mapped file
08 stycznia 2024 blog java jmh io
Jak najwydajniej odczytać plik?
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.