55
31

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

Last updated at Posted at 2023-12-06

今回は近年注目を浴びつつあるRustを使用してみようと思います。

Rustとは

Webブラウザ「Firefox」を開発しているMozillaが支援するオープンソースのプログラミング言語です。
CやC++に代わるメモリ使用量の効率化・信頼性、並列処理の安全性を目標とされています。
また、Rustは2016–2022年の間Stack Overflow Developer Surveyで「最も愛されているプログラミング言語」となっており、近年注目されています。

では早速環境構築から始めてみます。

環境構築

公式サイトからRustをダウンロードします。

image.png

はじめるからインストーラーをダウンロードすることができます。

次にダウンロードしたインストーラーを起動します。
RustにはVisual C++が必要となります。PCに入っていない場合は以下の画面が表示されるので1を選択してインストールします。

image.png

次にRustのインストールオプションについて聞かれるので1を選択しそのままインストールします。

image.png

以上で環境構築は完了となります。

HelloWorld

まずはHellowWorldのプログラムを作成してみます。

コマンドラインで以下を実行します。

cargo new hello-world

実行することで次のようなファイル構成が生成されます。

hello-world
  ・Cargo.toml
  ・src
    ・main.rs

Cargo.tomlはRust用の依存関係等を含むマニフェストファイルです。
ソースコードはsrc配下にあるmain.rsとなります。
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に記載します。

Cargo.toml
[dependencies]
jpeg-encoder = "0.6.0"
rayon = "1.8.0"

このファイルに必要なライブラリを記載することでビルド時に自動でライブラリを持ってきてくれます。

記載したrayonについて簡単に説明すると、
コードに並列処理を簡単に、安全に追加できるようにすることを目標としているライブラリとなります。

次にmainのソースを見ていきます。

main.rs
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やってみませんか?

55
31
2

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
55
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?