Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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 }

Slide 8

Slide 8 text

一時ファイルと組み合わせた大規模シャッフル① 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();

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

一時ファイルと組み合わせた大規模シャッフル 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で高速化可能

Slide 11

Slide 11 text

結果 速度計測 (メモリ 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万行

Slide 12

Slide 12 text

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