🤖
java.io Library Benchmark
各種 io library types
Byte Stream
- バイナリデータの操作
- テキストデータではなく、バイナリデータ(例:画像、音声、動画など)を扱う場合、 Byte IO を使用するのが最も直接的で適切(バイナリデータは特定の文字エンコーディングを持たず、バイトとしてそのまま読み書きされるため)
- データの精密な制御
- データの特定のbyteやbitを直接操作する必要がある場合、 Byte IO を使用すると、データへの精密なアクセスや制御が可能
- データの複雑な解釈や変換が不要なシンプルなタスクの場合、バイトI/Oを使用すると、オーバーヘッドを最小限に抑えることができる
- ストリームの連鎖
- データを複数のストリームやフィルタを通して処理する場合(例:圧縮、暗号化など)、 Byte IO を使用してデータを連鎖的に読み書きするのが効率的
- パフォーマンス
- 一部のシナリオでは、バッファリングや一度に大量のデータを読み書きすることで、バイトI/Oが高速に動作することがある
- 互換性
- 既存のシステムやプロトコルがバイトベースのデータを期待している場合、バイトI/Oを使用するのが最も簡単で互換性が高い
その他 IO Stream
- String Stream
- テキストファイルの読み書きに適切
- Java Class Samples
- java.io.FileReader
- java.io.FileWriter
- Object Stream
- Java Object の Serialize/Deserialize に利用
- オブジェクトをファイルやネットワーク経由で送受信することが可能
- Java Class Samples
- ObjectInputStream
- ObjectOutputStream
- Data Stream
- プリミティブデータ型(例: int, float など)の読み書きに使用
- java Class Samples
- DataInputStream
- DataOutputStream
Buffered Stream
データの読み書きを効率的に行うためのバッファリング機能を提供
- Sample Java Class
- BufferedWriter
- BufferedReader
- メリット
- バッファリングにより、ディスクやネットワークとのやり取りの回数が減少し、I/O操作が高速化します。
- 特にBufferedReaderには、テキストファイルから1行ずつ読み取るためのreadLineメソッドなど、便利なメソッドが提供されています。
バッファリングとは?
バッファリングは、データを一時的に保存する小さなメモリ領域(バッファ)を使用して、データの読み書きを効率的に行う技術です。
例えば、ファイルから1バイトずつデータを読み取るのではなく、一度に多くのバイトをバッファに読み込み、その後、バッファからデータを取得する方法があります。
このアプローチにより、ディスクやネットワークとのやり取りの回数が減少し、全体のパフォーマンスが向上します。
Memory IO Benchmark
@Fork(1)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5)
@Measurement(iterations = 10)
open class MemoryIOBenchmark {
private val content = "This is a sample content for benchmarking.".repeat(1000)
private val contentBytes = content.toByteArray()
// Byte Stream Write
@Benchmark
fun byteStreamWrite(): ByteArrayOutputStream {
val byteArrayOutputStream = ByteArrayOutputStream()
byteArrayOutputStream.write(contentBytes)
return byteArrayOutputStream
}
// Byte Stream Read
@Benchmark
fun byteStreamRead() {
val byteArrayInputStream = ByteArrayInputStream(byteStreamWrite().toByteArray())
while (byteArrayInputStream.read() != -1) {}
}
// Char Stream Write
@Benchmark
fun charStreamWrite(): StringWriter {
val stringWriter = StringWriter()
stringWriter.write(content)
return stringWriter
}
// Char Stream Read
@Benchmark
fun charStreamRead() {
val stringReader = StringReader(charStreamWrite().toString())
while (stringReader.read() != -1) {}
}
// Buffered Stream Write
@Benchmark
fun bufferedStreamWrite(): StringWriter {
val stringWriter = StringWriter()
BufferedWriter(stringWriter).use { it.write(content) }
return stringWriter
}
// Buffered Stream Read
@Benchmark
fun bufferedStreamRead() {
val stringReader = StringReader(bufferedStreamWrite().toString())
BufferedReader(stringReader).use { it.readLine() }
}
}
Memory IO Benchmark Result
Benchmark Mode Cnt Score Error Units
MemoryIOBenchmark.bufferedStreamRead thrpt 10 10.721 ± 0.803 ops/ms
MemoryIOBenchmark.bufferedStreamWrite thrpt 10 31.820 ± 2.154 ops/ms
MemoryIOBenchmark.byteStreamRead thrpt 10 17.787 ± 2.087 ops/ms
MemoryIOBenchmark.byteStreamWrite thrpt 10 485.193 ± 2.354 ops/ms
MemoryIOBenchmark.charStreamRead thrpt 10 1.498 ± 0.049 ops/ms
MemoryIOBenchmark.charStreamWrite thrpt 10 455.750 ± 78.862 ops/ms
- byte stream の書き込みは最も高速で、 string stream の読み込みは最も低速
- buffered stream は、大量のデータを扱う際に効果的ですが、小さなデータの操作には必ずしも最適ではない
Etc
このベンチマークは、メモリ上のI/O操作のパフォーマンスを測定している.実際のファイルやネットワークI/Oとは異なる結果になる可能性がある.
FILE IO Benchmark
repeat(n) の値を大きい値を取ることにより、大きめのdataを取り扱うようにしている
@Fork(1)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5)
@Measurement(iterations = 10)
open class FileIOBenchmark {
private val content = "This is a sample content for benchmarking.".repeat(100000)
private val contentBytes = content.toByteArray()
private val tempFile = File.createTempFile("benchmark", null)
@Setup
fun setUp() {
FileWriter(tempFile).use { it.write(content) }
}
@TearDown
fun tearDown() {
tempFile.delete()
}
// Byte Stream Read
@Benchmark
fun byteStreamRead() {
FileInputStream(tempFile).use { fis ->
while (fis.read() != -1) {}
}
}
// Byte Stream Write
@Benchmark
fun byteStreamWrite() {
FileOutputStream(tempFile, true).use { fos ->
fos.write(contentBytes)
}
}
// Char Stream Read
@Benchmark
fun charStreamRead() {
FileReader(tempFile).use { fr ->
while (fr.read() != -1) {}
}
}
// Char Stream Write
@Benchmark
fun charStreamWrite() {
FileWriter(tempFile, true).use { fw ->
fw.write(content)
}
}
// Buffered Stream Read
@Benchmark
fun bufferedStreamRead() {
BufferedReader(FileReader(tempFile)).use { br ->
while (br.readLine() != null) {}
}
}
// Buffered Stream Write
@Benchmark
fun bufferedStreamWrite() {
BufferedWriter(FileWriter(tempFile, true)).use { bw ->
bw.write(content)
}
}
}
FILE IO Benchmark Result
Benchmark Mode Cnt Score Error Units
FileIOBenchmark.bufferedStreamRead thrpt 10 0.145 ± 0.001 ops/ms
FileIOBenchmark.bufferedStreamWrite thrpt 10 0.142 ± 0.005 ops/ms
FileIOBenchmark.byteStreamRead thrpt 10 ≈ 10⁻⁴ ops/ms
FileIOBenchmark.byteStreamWrite thrpt 10 0.293 ± 0.051 ops/ms
FileIOBenchmark.charStreamRead thrpt 10 0.006 ± 0.001 ops/ms
FileIOBenchmark.charStreamWrite thrpt 10 0.126 ± 0.028 ops/ms
- ファイルの読み込みには、 buffered stream が最も効果的
- ファイルの書き込みには、 byte stream が最も効果的だが、string stream も競合するパフォーマンスを持っている
- byte stream を使用した FILE IO は、非常に低速であり、実際のアプリケーションでの使用は推奨されない
なぜ byte strema を使用した FILE IO は推奨されないのか?
- 単一バイトの読み込み/書き込み
- FileInputStream や FileOutputStream などの byte stream は、基本的には1バイトずつのデータを読み書きするため、大量のデータを処理する場合には非常に非効率
- バッファリングの欠如
- byte stream は、デフォルトでバッファリングしない
- これに対して、BufferedReader や BufferedWriter などの buffered stream は、内部的にデータのバッファを持っており、一度に複数のバイトや文字を読み書きすることができる
- これにより、I/O操作の回数が減少し、パフォーマンスが向上
- システムコールの頻度
- byte stream を使用すると、各バイトの読み書きごとにシステムコールが発生する可能性がある
- これは、オーバーヘッドが大きく、パフォーマンスに悪影響を及ぼす可能性がある
- byte stream を使用すると、各バイトの読み書きごとにシステムコールが発生する可能性がある
- 高レベルの操作の欠如
- バイトストリームは、基本的な読み書き操作のみを提供します。これに対して、 string stream や buffered stream は、行単位の読み書きやテキストのエンコーディング/デコーディングなど、より高レベルの操作をサポートしている
上記の理由から、大量のデータを効率的に読み書きする場合や、テキストファイルを扱う場合には、 byte stream よりも buffered stream や string stream を使用することが推奨される
Discussion