🤖

java.io Library Benchmark

2023/10/16に公開

各種 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 を使用すると、各バイトの読み書きごとにシステムコールが発生する可能性がある
      • これは、オーバーヘッドが大きく、パフォーマンスに悪影響を及ぼす可能性がある
  • 高レベルの操作の欠如
    • バイトストリームは、基本的な読み書き操作のみを提供します。これに対して、 string stream や buffered stream は、行単位の読み書きやテキストのエンコーディング/デコーディングなど、より高レベルの操作をサポートしている

上記の理由から、大量のデータを効率的に読み書きする場合や、テキストファイルを扱う場合には、 byte stream よりも buffered stream や string stream を使用することが推奨される

Discussion