12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RustのBufReader::seekはBufReader::readより遅い

Last updated at Posted at 2025-12-11

この記事は、 セゾンテクノロジー Advent Calendar 2025 の記事です。
シリーズ2では HULFTDataSpider の開発メンバーによる投稿をお届けします🕊️
12日目はHULFT10 for Container Servicesの開発者が担当します🙌

はじめに

皆さん、ファイルのseekとreadってどちらが速いと思いますか?
一般的に実際にデータを読まないseekの方が早そうですよね。

ですが、readよりseekの方が遅い。そんなケースがRustには存在します。

何が起きたか

以下のコードをご覧ください。seekとread、どちらが速いと思いますか?

use std::fs::File;
use std::io::prelude::*;
use std::io::{BufReader, SeekFrom};

const CHUNK: usize = 5;

fn read_chunk(reader: &mut BufReader<File>, buf: &mut [u8]) -> std::io::Result<bool> {
    match reader.read(buf) {
        Ok(0) => return Ok(false), // EOF
        Ok(_) => Ok(true),
        Err(e) => return Err(e),
    }
}

// read版
pub fn bufreader_read(filename: &str) -> std::io::Result<()> {
    let file = File::open(filename).unwrap();
    let mut reader = BufReader::new(file);
    let mut buf = [0u8; CHUNK];

    // readしてからreadを繰り返す
    while read_chunk(&mut reader, &mut buf)? {
        read_chunk(&mut reader, &mut buf).unwrap();
    }

    Ok(())
}

// seek版
pub fn bufreader_seek(filename: &str) -> std::io::Result<()> {
    let file = File::open(filename).unwrap();
    let mut reader = BufReader::new(file);
    let mut buf = [0u8; CHUNK];

    // readしてからseekを繰り返す
    while read_chunk(&mut reader, &mut buf)? {
        reader.seek(SeekFrom::Current(CHUNK as i64)).unwrap();
    }
    Ok(())
}

普通に考えたらseekの方が早そうですよね。

では、性能を比較してみましょう。
先ほどのコードで1MBのファイル読み込みをしてみます。
性能比較はRustのクレートであるcriterion.rsで計測します。

計測対象の関数 平均実行時間
bufreader_read 1.0266 ms
bufreader_seek 255.10 ms

圧倒的にseekが遅いですね。255倍も遅いです。不思議です。

原因

原因は以下のBufReader::seekの公式ドキュメントに書いてありました。
https://doc.rust-lang.org/std/io/struct.BufReader.html#method.seek

原文

Seeking always discards the internal buffer, even if the seek position would otherwise fall within it.

日本語訳(機械翻訳)

シークは、シーク位置が内部バッファ内に含まれる場合でも、常に内部バッファを破棄します。

ざっくり解釈すると、シークするたびに内部バッファ全部捨てて、データを読み直してるらしいです。そりゃ遅いですよね:rolling_eyes:

改善策

改善策も以下のBufReader::seekの公式ドキュメントに書いてありました。
https://doc.rust-lang.org/std/io/struct.BufReader.html#method.seek

原文

To seek without discarding the internal buffer, use BufReader::seek_relative.

日本語訳(機械翻訳)

内部バッファを破棄せずにシークするには、BufReader::seek_relative を使用します。

BufReader::seek_relativeを使えばいいらしいです。

というわけで実際に使ってみて性能を比較してみます。コードはこちら。

// seek_relative版
pub fn bufreader_seek_relative(filename: &str) -> std::io::Result<()> {
    let file = File::open(filename)?;
    let mut reader = BufReader::new(file);
    let mut buf = [0u8; CHUNK];

    // readしてからseek_relativeを繰り返す
    while read_chunk(&mut reader, &mut buf)? {
        reader.seek_relative(CHUNK as i64).unwrap();
    }

    Ok(())
}

結果はこうなりました。

計測対象の関数 平均実行時間
bufreader_read 1.0266 ms
bufreader_seek 255.10 ms
bufreader_seek_relative 720.18 µs

seek_relative版が一番速いことが分かります。マイクロ秒単位なのでかなり高速です。

最後に

割と直感的ではない仕様だと思うのでまんまとハマりました。

私はこの処理を製品コードに埋め込んで、テストのタイミングで「何か一部の処理が異常に遅くないか?」となりこの問題に気づけました。リリース前に検知できて良かったです。

皆さんもRustのBufReaderでreadやseekを繰り返すような処理を行う場合はご注意ください。

余談

今回はRustのBufReaderが内部バッファを破棄してしまうことが原因で遅くなりましたが、他にも同じような実装してる言語ってあるんでしょうか。気になったので今度調べてみようと思います。

12
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?