Rust を使って、テキストファイルを開いて中のデータを読み出すということをやってみます。Rust では読み込み方法がいくつもあるのでそれの比較と、普段私が使っている Python の numpy.loadtxt
との比較も行っていきます。
環境
読み込むデータ
時間を計測したいので、そこそこ大きなテキストファイルを用意します。ここは慣れている Python で行いました。
data = "" num = 1000000 for i in range(num): data += f"{i} {i*2}" if i < num - 1: data += "\n" with open("example.txt", "w") as f: f.write(data)
これを実行すると、タブで区切られた100万行2列のテキストデータが生成されます。
Rust でテキストファイルを読み込む
最初に、Rust を使ってこのテキストファイルを読み込んでx
, y
という名前の2つのベクトルにデータを入れるコードを書いてみます。
read_to_string
を使う方法
まずは標準ライブラリに入っている関数の一つであるread_to_string
を使って書いてみます。
use std::fs::read_to_string; pub fn open_file(filename: &str) -> (Vec<f32>, Vec<f32>){ let data = read_to_string(filename); let xy = match data { Ok(content) => content, Err(error) => {panic!("Could not open or find file: {}", error);} }; let xy_pairs: Vec<&str> = xy.trim().split("\n").collect(); let mut x: Vec<f32> = Vec::new(); let mut y: Vec<f32> = Vec::new(); for pair in xy_pairs { let p: Vec<&str> = pair.trim().split(" ").collect(); x.push(p[0].parse().unwrap()); y.push(p[1].parse().unwrap()); } (x, y) }
時間を計測してみると、
time cargo run --release real 0m0.248s user 0m0.156s sys 0m0.047s
BufReader
を使う方法
Rust には std::io
にもファイル読み込みができる関数が用意されているのでこっちを使ってみます。
use std::fs::File; use std::io::{BufReader, BufRead}; pub fn open_file(filename: &str) -> (Vec<f32>, Vec<f32>){ let f = File::open(filename).unwrap(); let buf = BufReader::new(f); let mut x: Vec<f32> = Vec::new(); let mut y: Vec<f32> = Vec::new(); for line in buf.lines() { let l: &str = &line.unwrap(); let p: Vec<&str> = l.trim().split(" ").collect(); x.push(p[0].parse().unwrap()); y.push(p[1].parse().unwrap()); } (x, y) }
時間は
time cargo run --release real 0m0.264s user 0m0.188s sys 0m0.047s
遅くなった(?)。
split_whitespace
を使う方法
一番最初の方法では trim().split(" ")
と、いちいち空白文字で分けるということをこっちで指定して分割を行っていました。しかし、Rust にはそれをやるための関数 split_whitespace
が用意されているのでこっちを使ってみます。これは一番最初のコードでtrim().split(" ")
の部分を split_whitespace()
に書き換えるだけです。
時間を計測すると、
time cargo run --release real 0m0.265s user 0m0.188s sys 0m0.063s
あんまり変わらない。
文字列から浮動小数点数への変換にlexical
を使う
このコードをプロファイリングしてみるとわかるのですが、実行時間の40 % が 文字列処理で取られています。ファイルの読み込みとは少し話がずれますが、ここも別の方法を試してみます。&str
から f32
への変換にここまでのコードでは parse()
を使ってきました。しかし調べてみると、どうやらlexical
というクレートが高速変換を謳っているということが分かりました。これを試してみます。
use lexical;
をしたあとに次の部分を変更します。
// before x.push(p[0].parse().unwrap()); y.push(p[1].parse().unwrap()); // after x.push(lexical::parse(p[0]).unwrap()); y.push(lexical::parse(p[1]).unwrap());
time cargo run --release real 0m0.789s user 0m0.516s sys 0m0.203s
今までで一番遅いですね。。。(なんで?)
(おまけ) numpy.loadtxt
との比較
最後に、Numpy を使ったときの Python との比較をしてみます。
import numpy as np x, y = np.loadtxt("example.txt").T
超簡単!! やはり Python で書くとシンプルですね。さて、時間は
real 0m13.730s user 0m12.297s sys 0m1.438s
遅い。ちなみにimport numpy as np
の部分は 0.39 secくらいなのでインポートに時間が食われているわけではない。さすがに Numpy でもテキスト読み込み処理に関してはそんなに早くないのか?
まとめ
read_to_string
が一番速かった- Python より全然速い!
- もっと速くする方法知りたい(&str -> f32 とか特に)
![実践Rust入門[言語仕様から開発手法まで] 実践Rust入門[言語仕様から開発手法まで]](https://images-fe.ssl-images-amazon.com/images/I/51e5B1Zx%2ByL._SL160_.jpg)
- 作者: κeen,河野達也,小松礼人
- 出版社/メーカー: 技術評論社
- 発売日: 2019/05/08
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る