RustのCrate調査 (version_check, ryu, unicode-width, fnv)

先日、Rust言語でよく使われているcrateをいくつか調べました。

toyamaguchi.hatenablog.com

今回は、version_check、ryu、unicode-width、fnvについて調査をしました。

コンパイラのバージョンは「rustc 1.33.0」です。

version_check : コンパイラのバージョンをチェック

version_checkは、現在の開発環境にあるコンパイラのバージョンチェックを行うcrateです。

注意しなくてはいけないことは、コンパイル時のコンパイラのバージョンではないところです。

このcrateの内部では、コマンドで「rustc --version」のプロセスを起動して、そこからバージョン情報を得ています。

Rustの開発ツールを作るときなど、コンパイラのバージョンに依存したコードを書くときに利用できますね。

サンプルコードは次のようになります。

crateのバージョンは、「version_check = "0.1.5"」です。

use version_check;

pub fn run() {
    println!("##### version_check #####");
    println!("{}", match version_check::is_nightly() { // nightly or not
        Some(true) => "running a nightly",
        Some(false) => "not nightly",
        None => "couldn't figure it out"
    });
    println!("{}", match version_check::is_min_version("1.13.0") { // compare by version
        Some((true, version)) => format!("Yes! It's: {}", version),
        Some((false, version)) => format!("No! {} is too old!", version),
        None => "couldn't figure it out".into()
    });
    println!("{}", match version_check::is_min_date("2016-12-18") { // compare by date
        Some((true, date)) => format!("Yes! It's: {}", date),
        Some((false, date)) => format!("No! {} is too long ago!", date),
        None => "couldn't figure it out".into()
    });
}

出力は次のようになります。

##### version_check #####
not nightly
Yes! It's: 1.33.0
Yes! It's: 2019-02-28

ryu : 浮動小数点数を高速に文字列に変換

ryuは、浮動小数点数を高速に文字列に変換するcrateとのことです。

そんなことにわざわざcrateが必要なのか、と思ってしまいますが、情報工学の素養がある方は、浮動小数点数の扱い方が少しやっかいなことをご存じの方も多いはずです。

どうもドキュメントによると、こちらの論文の技術を利用することで、高速に正確に浮動小数点数を文字列に直すことができるらしいのです。 (Google GermanyのUlf Adams氏の論文のようです。)

ryuの使い方は次のようになります。

use ryu;

pub fn run() {
    println!("##### ryu #####");
    let mut buffer = ryu::Buffer::new();
    println!("{}", buffer.format(1.234));
    println!("{}", buffer.format(0.000001));
    println!("{}", buffer.format(-0.000001));
    println!("{}", buffer.format(std::f32::INFINITY));
    println!("{}", buffer.format(std::f32::NAN));
}

出力は次のようになります。

##### ryu #####
1.234
1e-6
-1e-6
1.0737418e40
1.6106127e40

ドキュメントにも書いてありますが、無限 (std::f32::INFINITY)、NaN (std::f32::NAN)には対応していないので、気にする場合は直前にis_finite()やis_nan()で確認が必要です。

unicode-width : Unicode文字列の文字幅を計算

Unicode文字列の表示幅を計算してくれるcrateです。

Unicode文字列の場合、実際の表示幅とバイト数が異なるため、このようなcrateが必要になります。

サンプルコードは次のようになります。

UnicodeWidthStrはstrに対するのtraitになっています。

use unicode_width::UnicodeWidthStr;

pub fn run() {
    println!("##### unicode_width #####");
    let teststr = "ハローワールド";
    println!("{}", teststr);
    println!("The above string length is {}.", teststr.len());
    println!("The above string is {} columns wide.", teststr.width());
    println!("The above string is {} columns wide (CJK).", teststr.width_cjk());

    let teststr = "Hello, world!";
    println!("{}", teststr);
    println!("The above string length is {}.", teststr.len());
    println!("The above string is {} columns wide.", teststr.width());
    println!("The above string is {} columns wide (CJK).", teststr.width_cjk());
}

出力は次のようになります。

##### unicode_width #####                 
ハローワールド
The above string length is 21.
The above string is 14 columns wide.
The above string is 14 columns wide (CJK).
Hello, world!
The above string length is 33.
The above string is 23 columns wide.
The above string is 23 columns wide (CJK).

日本語を画面に表示するときの、レイアウトの調節に使えそうですね。

fnv : Fowler–Noll–Vo ハッシュ関数

このcrateは、Fowler–Noll–Vo ハッシュ関数の実装(HashMap、HashSet)が含まれているcrateです。

Fowler–Noll–Voというのは、このハッシュ関数を作った3人の名前 (Glenn Fowler、Landon Curt Noll、Kiem-Phong Vo)が由来だそうです。

どうもデフォルトのHashMapは、integer型のような小さいデータ型をキーとして使った場合、処理が遅いようです。

たいていの場合、キーはそれほど大きくないと思います。そのような場合には、このfnvを使ったほうが高速に処理ができるらしいです。

ただし、キーが大きい場合には、fnvはとても遅いらしいので、使い分けに注意して使いましょう。

crates.ioのfnvのページにリンクが貼ってあった、ハッシュ関数の比較ページの中にグラフが貼ってあったので、そちらを参考にしてください。

f:id:toyamaguchi:20190403133336p:plain
Comparison of Hash Function

サンプルコードはシンプルです。

use fnv::FnvHashMap;

pub fn run() {
    println!("##### fnv #####");
    let mut map = FnvHashMap::default();
    map.insert(1, "one");
    map.insert(2, "two");
    println!("{} {}", map.get(&1).unwrap(), map.get(&2).unwrap());
}

出力は次のようになります。

##### fnv #####
one two

使い方は普通のHashMapと変わりませんね。

まとめ

今回は、version_check、ryu、unicode-width、fnvについて調査を行いました。

また、気になるcrateがあったら調査していこうと思います。

参考文献

RustのCrate調査 (bitflags, byteorder, chrono, encoding_rs, num_cpus)

Rustのコードをコンパイルしていると、直接的には使っていないですが、依存関係のためにダウンロードされてくるcrateをよく見ます。

そういうcrateの中には便利なものもあるだろうと思い、いくつかのcrateの調査をしてみました。

今回は、bitflags、byteorder、chrono、encoding_rs、num_cpusの5つについて調べました。

コンパイラのバージョンは「rustc 1.33.0」です。

bitflags : 定数として使うビットフラグを簡単に構築

bitflagは、Linux環境のファイルパーミッションのような、ビットフラグを定義するのを助けるcrateです。

実際にサンプルコードとして、ファイルパーミッションのようなビットフラグを定義してみました。

crateのバージョンは、「bitflags = "1.0.4"」です。

use bitflags::bitflags;

bitflags! {
    struct FileFlags: u32 {
        const EXEC = 0b00000001;
        const WRITE = 0b00000010;
        const READ = 0b00000100;
        const RWX = Self::READ.bits | Self::WRITE.bits | Self::EXEC.bits;
    }
}

pub fn run() {
    println!("##### bitflags #####");
    let file_flags = FileFlags::READ | FileFlags::WRITE; // OR
    println!("OR: {:?}", file_flags);
    let file_flags = FileFlags::READ & FileFlags::WRITE; // AND
    println!("AND: {:?}", file_flags);
    let file_flags = FileFlags::RWX - FileFlags::WRITE; // Sub
    println!("Sub: {:?}", file_flags);
    let file_flags = FileFlags::RWX - FileFlags::WRITE - FileFlags::WRITE; // Sub2
    println!("Sub2: {:?}", file_flags);
}

出力は次のようになります。

##### bitflags #####
OR: WRITE | READ
AND: (empty)
Sub: EXEC | READ
Sub2: EXEC | READ

引き算の演算子は、きちんと内部でビット演算しているので、2回引いてもおかしなビットにならないようです。

byteorder : バイト列のエンディアンを指定

byteorderはデータを扱う際に、リトルエンディアンかビッグエンディアンかを指定することができます。

crateのバージョンは、「byteorder = "1.3.1"」です。

use byteorder::{ReadBytesExt, BigEndian, LittleEndian};

pub fn run() {
    println!("##### byteorder #####");
    let bytes: Vec<u8> = vec![0, 0, 255, 255];
    println!("Big Endian: {}", (&bytes[0..4]).read_u32::<BigEndian>().unwrap());
    println!("Little Endian: {}", (&bytes[0..4]).read_u32::<LittleEndian>().unwrap());
}

出力は次のようになります。

##### byteorder #####
Big Endian: 65535
Little Endian: 4294901760

ビッグエンディアンは、バイトの上位から読んで、65535=0x0000ffff。

トルエンディアンは、バイトの下位から読んで、16842752=0xffff0000。

今回はデータをu32で読み取る(read_u32)ことをしていますが、他の型(u16、u64など)のための読み込み関数も各種用意されており、それらはbyteorder::ReadBytesExtで定義されています。

また、書き込み用のbyteorder::WriteBytesExtも定義されています。

ネットで検索していたところ、PNG画像のデータをBigEndianで読み取る場面で使用している例がありました。データ構造によっては、エンディアンが指定されているものもあるのかもしれませんね。

chrono : 時間・日付を操作を便利に

chronoは、UTCと現地時間の変換や、文字列から時間データへの変換などの操作が簡単にできます。

crateのバージョンは、「chrono = "0.4.6"」です。

use chrono::prelude::*;

pub fn run() {
    println!("##### chrono #####");
    let utc: DateTime<Utc> = Utc::now();
    println!("UTC: {}", utc);
    println!("Convert from UTC to Local Timezone: {}", utc.with_timezone(&Local));
    println!("Convert from UTC to Unix Timestamp: {}", utc.timestamp());
    println!("");

    let local: DateTime<Local> = Local::now();
    println!("Local: {}", local);
    println!("Convert from Local Timezone to UTC: {}", local.with_timezone(&Utc));
    println!("Convert from Local to Unix Timestamp: {}", local.timestamp());
    println!("");

    let dt = Utc.timestamp(1_500_000_000, 0);
    println!("Convert from Unix Timestamp: {}", dt);
    println!("RFC3339: {}", dt.to_rfc3339());
    println!("");

    if let Ok(dt) = "2014-11-28T12:00:09Z".parse::<DateTime<Utc>>() {
        println!("Convert from String: {}", dt);
    }
}

出力は次のようになります。

##### chrono #####
UTC: 2019-03-30 12:53:56.797502255 UTC
Convert from UTC to Local Timezone: 2019-03-30 21:53:56.797502255 +09:00
Convert from UTC to Unix Timestamp: 1553950436

Local: 2019-03-30 21:53:56.797587758 +09:00
Convert from Local Timezone to UTC: 2019-03-30 12:53:56.797587758 UTC
Convert from Local to Unix Timestamp: 1553950436

Convert from Unix Timestamp: 2017-07-14 02:40:00 UTC
RFC3339: 2017-07-14T02:40:00+00:00

Convert from String: 2014-11-28 12:00:09 UTC

encoding_rs : Webブラウザでも使われているエンコーディング機能を提供

encoding_rsは、エンコーディングのためのcrateです。

GitHubのREADMEによると、レンダリングエンジン「Gecko」でも使われているようです。

crateのバージョンは、「encoding_rs = "0.8.17"」です。

use encoding_rs::*;

pub fn run() {
    println!("##### encoding_rs #####");
    let bytes = b"\x83n\x83\x8D\x81[\x81E\x83\x8F\x81[\x83\x8B\x83h"; // SJIS

    let (cow, encoding_used, had_errors) = SHIFT_JIS.decode(bytes);
    println!("bytes: {:?}", bytes);
    println!("cow: {}", cow);
    println!("encoding: {:?}", encoding_used);
    println!("error: {}", had_errors);
}

出力は次のようになります。

##### encoding_rs #####
bytes: [131, 110, 131, 141, 129, 91, 129, 69, 131, 143, 129, 91, 131, 139, 131, 104]
cow: ハロー・ワールド
encoding: Encoding { Shift_JIS }
error: false

Shift JISの文字列が変換されてプリントできるようになりました。

Webから取得した文字列には、様々なエンコーディングのものもあるので、文字列を変換したいときに重宝しそうです。

num_cpus : CPUの数を取得

num_cpusは、その名の通りCPUの数を取得できるcrateです。

use num_cpus;

pub fn run() {
    println!("##### num_cpus #####");
    println!("num of cpus: {}", num_cpus::get());
    println!("num of physical core: {}", num_cpus::get_physical());
}

出力は次のようになります。

##### num_cpus #####
num of cpus: 4
num of physical core: 2

実際に計算できるCPUのスレッド数と、物理的なCPUのコア数が、取得できます。

Rustでコマンドラインツールを作っている人も多いと思いますが、重い処理の高速化を図るためにマルチスレッドにしている場合は、この値を参考にしてスレッドの数を調節できそうですね。

まとめ

今回は、bitflags、byteorder、chrono、encoding_rs、num_cpusの5つについて調べました。

また、他のcrateについても調べていこうと思います。

情報処理学会 第81回 全国大会に参加しました

3月14日から3日間、福岡で開催されていた「情報処理学会 第81回 全国大会」に参加してきました。

f:id:toyamaguchi:20190326232126j:plain

情報処理学会の公式ページによれば、全国大会は一年に一度開催される、学会最大のイベントとのことです。 私もこの全国大会に参加したことで、学会がカバーする様々な分野の研究発表や、有名な先生をお招きしての特別講演、様々なテーマの企画など、興味深いコンテンツをたくさん見ることができました。

最近、私は、情報処理系の研究分野から知識を得ようと、論文を読むことを始めたのですが、全国大会でも多くの論文を読むことができ、様々な研究に触れることができました。

全国大会で発表される論文は、一つずつの長さが2ページと、やや短いです。 そのため、余分な文章は削ぎ落とされ、必要なところだけが残っているような論文に仕上がっています。 読む側からすると、日本語であることに加えてページ数も短いため、次々と論文を読み進むことができました。

全国大会で発表される論文は、研究を始めたばかりの学生のものから、一般企業からのものもあります。 そのため、多くの論文を読んでいると、だんだんと論文の良し悪しがわかってきてとても良いです。 学会への論文投稿を目指し始めた学生の方たちも、まずは全国大会の論文をたくさん読んでみて、良い構成の論文の書き方を真似してみたらどうでしょうか。

また、様々な研究の発表の場で交わされる質疑応答の中で、「なるほど」と思ったこともありました。 現在行っている研究の全体像はどのくらいの規模で、そのうちのどれくらいの割合が今回の実験でカバーできたのか、という内容を論文に加えるとさらに良くなる、という内容のコメントでした。

全国大会のセッションの一つにあった「論文必勝法」のパネル討論の中でも話されていましたが、研究の目標が100%達成できていなくても、現在までにできたことを論文の体裁で書くことができれば、採録は十分にありえるとのことです。

研究の達成率を100%にすることは難しかったりしますが、ある程度の区切りで論文を投稿してみるのもいいですね。

Shinjuku.rs #3にてLightning Talkをしました。

先日、3月12日に行われた勉強会「Shinjuku.rs #3」にて、Lightning Talkをしました。

発表は「Visualizatoin System in Rust - Rustで作る可視化サーバの紹介」というタイトルで行いました。

www.slideshare.net

CTFなどで活躍するOpenGL製の可視化サーバをRustで組んでみたとき、どのような利点があるのか、はたまたどんな問題に遭遇したのか発表しました。

OpenGLを使うシステムで、Rustを使う良い点は次のようなところがありました。

  1. 処理速度が速い
  2. ガベッジコレクションがない
  3. クロスプラットフォームで動作するアプリが作りやすい
  4. 新規ライブラリの導入が楽

一方で、いくつものスレッドが実行される並列処理のプログラムを組もうと思ったときに、スレッドをまたいだデータの扱いが難しく、初心者には大きな障壁になりえることを発表しました。

私自身、Rustの習熟度がまだまだなので、これからも勉強して、並列処理のうまい攻略法を探っていきたい所存です。

ただ、わからないなりに発表して良かったことは、参加者のκeenさんやqnighyさんからアドバイスをもらうことができたことです。

Rustコミュニティは、Rust自体が難しいせいか(笑)、みなさん優しいです。

発表 = 自己紹介、くらいの気持ちで発表すると、他の方たちとも早く打ち解けられるので、他の方たちもどんどん発表できたらいいなぁ、と思っています。