今回は近年注目を浴びつつあるRustを使用してみようと思います。
Rustとは
Webブラウザ「Firefox」を開発しているMozillaが支援するオープンソースのプログラミング言語です。
CやC++に代わるメモリ使用量の効率化・信頼性、並列処理の安全性を目標とされています。
また、Rustは2016–2022年の間Stack Overflow Developer Surveyで「最も愛されているプログラミング言語」となっており、近年注目されています。
では早速環境構築から始めてみます。
環境構築
公式サイトからRustをダウンロードします。
はじめるからインストーラーをダウンロードすることができます。
次にダウンロードしたインストーラーを起動します。
RustにはVisual C++が必要となります。PCに入っていない場合は以下の画面が表示されるので1を選択してインストールします。
次にRustのインストールオプションについて聞かれるので1を選択しそのままインストールします。
以上で環境構築は完了となります。
HelloWorld
まずはHellowWorldのプログラムを作成してみます。
コマンドラインで以下を実行します。
cargo new hello-world
実行することで次のようなファイル構成が生成されます。
hello-world
・Cargo.toml
・src
・main.rs
Cargo.tomlはRust用の依存関係等を含むマニフェストファイルです。
ソースコードはsrc配下にあるmain.rsとなります。
main.rsの中身は以下となります。
fn main() {
println!("Hello, world!");
}
これだけ見るとCやC++に似ているように見えますね。
では、「hello-world」ディレクトリで次のコマンドを実行してHelloWorldを実行してみます。
cargo run
次のようなアウトプットが表示され、「Hello,World!」が表示されておりプログラムが実行されていることがわかります。
C:\src\hello-world> cargo run
Compiling hello-world v0.1.0 (C:\src\hello-world)
Finished dev [unoptimized + debuginfo] target(s) in 1.66s
Running `target\debug\hello-world.exe`
Hello, world!
並列アプリケーション
次にRustが売りとしている並列処理を組み込んだアプリケーションを作成してみます。
アプリケーションの内容としては、jpgファイルの圧縮率を小さくするアプリケーションとします。
まずはアプリケーションで画像ファイルを操作するために必要なライブラリ、並列処理に必要なライブラリの依存関係をCargo.tomlに記載します。
[dependencies]
jpeg-encoder = "0.6.0"
rayon = "1.8.0"
このファイルに必要なライブラリを記載することでビルド時に自動でライブラリを持ってきてくれます。
記載したrayonについて簡単に説明すると、
コードに並列処理を簡単に、安全に追加できるようにすることを目標としているライブラリとなります。
次にmainのソースを見ていきます。
use std::fs;
use std::time::Instant;
use jpeg_encoder::Encoder as JpegEncoder;
use jpeg_encoder::ColorType;
use rayon::prelude::*;
fn main() {
let start = Instant::now();
// ディレクトリ内容を取得
let files : Vec<_> = fs::read_dir("./data").unwrap()
.filter_map(|x| x.ok())
.collect();
files.par_iter().enumerate()
.for_each(|(_index, entry)| {
// ファイル名を取得
let img_name = &entry.file_name().into_string().unwrap();
// 画像を開く
let open_data = format!("{}{}", "./data/" ,img_name);
let img = image::open(open_data).unwrap();
// jpeg圧縮率50%で画像を保存
let img_out_name = format!("{}{}", "./data/OUT_" ,img_name);
let encoder = JpegEncoder::new_file(img_out_name, 50).unwrap();
let _ = encoder.encode(&img.to_rgb8(), img.width() as u16, img.height() as u16, ColorType::Rgb);
});
let end = start.elapsed();
println!("処理時間:{}.{:03}秒", end.as_secs(), end.subsec_nanos() / 1_000_000);
}
各コードについて説明していきます。
use std::fs;
use std::time::Instant;
use jpeg_encoder::Encoder as JpegEncoder;
use jpeg_encoder::ColorType;
use rayon::prelude::*;
ここでは使用するライブラリの宣言を行っています。上二つについては標準のライブラリ、それから下は追加したライブラリの宣言となります。
// ディレクトリ内容を取得
let files : Vec<_> = fs::read_dir("./data").unwrap()
.filter_map(|x| x.ok())
.collect();
ここではrayonで並列処理をするための元データを設定します。
カレントディレクトリのdataフォルダ内のファイルをデータとして設定します。
files.par_iter().enumerate()
.for_each(|(_index, entry)| {
// ファイル名を取得
let img_name = &entry.file_name().into_string().unwrap();
// 画像を開く
let open_data = format!("{}{}", "./data/" ,img_name);
let img = image::open(open_data).unwrap();
// jpeg圧縮率50%で画像を保存
let img_out_name = format!("{}{}", "./data/OUT_" ,img_name);
let encoder = JpegEncoder::new_file(img_out_name, 50).unwrap();
let _ = encoder.encode(&img.to_rgb8(), img.width() as u16, img.height() as u16, ColorType::Rgb);
});
ここでは先ほどのディレクトリ内の各ファイルについてrayonを用いて並列処理を行います。
処理内容は30mbのjpgファイル10枚を各画像の圧縮率を小さくすることです。
files.par_iter().enumerate()
.for_each(|(_index, entry)| {
この記載は先ほどrayonで並列処理をするための元データを設定したfilesに対してpar_iter().enumerate().for_eachが記載されています。
これによりrust側でforeach内の処理を並列で実行することができます。
ちなみに(_index, entry)は、_indexはインデックス番号、entryは今回はフォルダから取得したファイル情報となります。
// ファイル名を取得
let img_name = &entry.file_name().into_string().unwrap();
// 画像を開く
let open_data = format!("{}{}", "./data/" ,img_name);
let img = image::open(open_data).unwrap();
// jpeg圧縮率50%で画像を保存
let img_out_name = format!("{}{}", "./data/OUT_" ,img_name);
let encoder = JpegEncoder::new_file(img_out_name, 50).unwrap();
let _ = encoder.encode(&img.to_rgb8(), img.width() as u16, img.height() as u16, ColorType::Rgb);
この記載はファイル操作となります。
ファイル名を取得して画像をopenしてimageとして取得します。
そのimageをJpegEncoderを用いて圧縮率を50%まで下げる処理を実施します。
最後にencoder.encodeを用いてファイルに保存する処理を実施します。
この画像処理箇所をループさせています。
このプログラムを実行し、処理時間についてみてみます。
C:\src\parallel-exp> cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.12s
Running `target\debug\parallel-exp.exe`
処理時間:22.098秒
結果はこのようになりました。
並列処理を実施していない処理の時間も見てみましょう。
C:\src\parallel-exp> cargo run
Finished dev [unoptimized + debuginfo] target(s) in 1.33s
Running `target\debug\parallel-exp.exe`
処理時間:130.920秒
当然のことですがスレッドで並列処理するほうが早いですね。
まとめ
今回の初めてのRustいかがだったでしょうか。
私は並列処理が簡単に使えるというところで使える場面があるのでは...と思った次第です。今後もぜひ使ってみたいと思います。
また、使用できるライブラリに豊富にあったのでやりたいことの実現性については高そうに思えます。
皆さんもRustやってみませんか?