Upgrade to Pro — share decks privately, control downloads, hide ads and more …

巨大なサイズのファイルの行をシャッフルする

ctylim
November 28, 2022

 巨大なサイズのファイルの行をシャッフルする

ctylim

November 28, 2022
Tweet

More Decks by ctylim

Other Decks in Programming

Transcript

  1. 巨大なサイズのファイルの行を
    シャッフルする
    Rust、何もわからない... #5
    2022.11

    View Slide

  2. 自己紹介
    ctyl
    本名:四方 (シカタ)
    GitHub @ ctylim
    UNICORN株式会社 ソフトウェアエンジニア 2018/11〜
    主な業務:web広告入札アルゴリズム(RTB・検索型)・インフラコスト節約
    社内Rust活用実績:広告価値予測のための学習ロジック(Pure Rust)・CLIツール等
    好き:日本酒・旅行・競技プログラミング
    2

    View Slide

  3. 本日紹介する内容
    巨大なサイズのファイルの行をシャッフルする
    1. 事例:UNICORN社内で使われているRust製ツール
    2. メモリに入らない巨大なサイズ・行数のファイルをシャッフルする
    3. 業務でRustを使った開発をするには
    3

    View Slide

  4. 社内のとある機械学習ワークフロー
    4
    データソース
    シャッフル
    生データ
    (CSV)
    学習用データ
    (CSV)
    シャッフルの対象となるデータは数百GB, 数億行規模
    ストリーム
    出力
    標準入出力

    View Slide

  5. 先行ソフトウェア:terashuf
    terashuf (https://github.com/alexandres/terashuf)
    ● C++製
    ● 昔社内でこれを使っていた
    ● ファイル入出力のみ
    ● (当時)CSVヘッダ行飛ばし等のオプションなし
    ● (当時)厳格なrandom shuffleではない
    → 厳格な:全ての (i, j) に対して、i 行目がシャッフル後
                       j 行目に移動する確率が 1/総行数
    5

    View Slide

  6. 要件
    ● メモリサイズに実行可否が依存しない行のシャッフル
    ● 高速に動作する:少なくとも既存ソフトウェア以上
    ● ヘッダ飛ばし等もできる
    ● 厳格なrandom shuffle
    CLIツールの開発言語としてRustを採用
    rhuffle (https://github.com/ctylim/rhuffle)
    6

    View Slide

  7. Fisher-Yates shuffle
    配列をランダムに並べ替えるアルゴリズム
    配列長 N に対して時間・空間計算量 O(N)
    7
    pub fn fisher_yates_shuffle_n(n: usize) -> Vec {
    let mut v: Vec = Vec::with_capacity(n);
    for i in 0..n {
    v.push(i);
    }
    let mut rng = thread_rng();
    for i in (1..n).rev() {
    let r: usize = rng.gen();
    v.swap(i, r % (i + 1));
    }
    v
    }

    View Slide

  8. 一時ファイルと組み合わせた大規模シャッフル①
    8
    1. メモリに入る分まで読んでシャッフル
    2. 一時ファイルに書き出し
    を繰り返す

    100GB
    CSV
    4GB
    4GB
    let file = NamedTempFile::new()?;
    let shuf_indices = fisher_yates_shuffle_n(rows.len());
    let mut tmp_writer = io::writer(file.path().to_str()?;
    for i in shuf_indices {
    tmp_writer.write(format!("{}", rows[i]).as_bytes())?;
    }
    tmp_files.push(TmpFile {
    remaining_rows: rows.len(),
    file: file,
    };
    total_rows += rows.len();

    View Slide

  9. 一時ファイルと組み合わせた大規模シャッフル②
    9
    TempFile 1 TempFile 2 TempFile 3
    残り100行
    残り100行
    残り50行
    出力
    1行読み進める確率
    1行書き込む
    重み付きランダムにファイルを
    選択し1行読み進めることで、
    全行のrandom shuffleを実現

    View Slide

  10. 一時ファイルと組み合わせた大規模シャッフル
    let mut rng = thread_rng();
    for i in 0..total_rows {
    let r: usize = rng.gen_range(0, total_rows - i) + 1;
    let mut rows = 0;
    for j in 0..tmp_files.len() {
    rows += tmp_files[j].remaining_rows;
    if r <= rows {
    let mut buf = String::new();
    read_line(&mut tmp_file_readers[j], &mut buf)?;
    writer.write(format!("{}", buf).as_bytes())?;
    tmp_files[j].remaining_rows -= 1;
    break;
    }
    }
    }
    10
    残り行数に対する
    重み付きランダムで
    一時ファイルを選ぶ
    1行読み進める
    ※内側のforループはBinary Indexed Treeで高速化可能

    View Slide

  11. 結果
    速度計測 (メモリ 16GB, 一時ファイルサイズ 4GB)
    使用感
    ● (わかってはいたけど) Disk IOがボトルネック
    ○ 出力時ファイル分割・並列化 など...
    ● ワークフロー全体だとシャッフル前後の処理がボトルネックになる
    ○ データのクエリ・整形等
    11
    Software real user sys
    GNU shuf 0m59s 0m34s 0m14s
    terashuf 5m06s 4m43s 0m14s
    rhuffle 1m56s 1m06s 0m40s
    Software real user sys
    GNU shuf x x x
    terashuf 8m12s 7m16s 0m31s
    rhuffle 1m47s 0m39s 0m51s
    5.3GB, 5500万行 9.0GB, 2200万行

    View Slide

  12. 業務でRustを使った開発をするには...
    Rustは大半の企業で事業のコアを担う開発言語として道半ば
    >>> これから使われるようになる <<<
    条件:
    ● 高速に動作する・メモリ効率・安全性に優れたシステム要請
    ● Rustを書くことに積極的な人が社内に多い
    CLIツール(→マイクロサービス)→プロジェクト と活用範囲を広げる
    何より生産性の高いソフトウェアを書くことが大切...
    12
    Rustで書いたソフトウェアでサービスの価値を高めよう

    View Slide