【Rust】【TypeScript(JavaScript)】実行速度の比較

tl;dr

Rust は TypeScript(JavaScript) より早い

バージョン

Rust

cargo 1.28.0 (96a2c7d16 2018-07-13)

TypeScript(JavaScript)

node v11.2.0
tsc Version3.1.3

内容

事の発端

「Rustは安全性、速度、並行性の3つのゴールにフォーカスしたシステムプログラミング言語です。」と書かれているのをドキュメントで見かけ、ではどの程度早いのか知りたくなったのが事の発端です。

ルール

数独の回答を検証するスクリプトを実行し、完了速度を比較します。この時、以下の条件に従ったコードを書きます。

      入力は数独の正解データ1つを用いる。この時、データの形式は左上が1文字目として右に向かって順に数えていき、右下を81文字目とした半角数字81桁の文字列とする。
      タテ列、ヨコ列、3×3のブロックを9つずつ、それぞれ27個に1から9の数字が入っていることの検証を1回とし、100000回実行した際の完了時間を比較する。

Rust のコード

use std::time::Instant;

fn main() {
    // 初期条件
    let target: Vec = "864371259325849761971265843436192587198657432257483916689734125713528694542916378".chars().collect();

    let start = Instant::now();

    for _bench in 0..100000 {
        for x in 0..9 {
            // 横
            let segment_record = x * 9;
            is_fill_condition_9(target[segment_record + 0],target[segment_record + 1],target[segment_record + 2],target[segment_record + 3],target[segment_record + 4],target[segment_record + 5],target[segment_record + 6],target[segment_record + 7], target[segment_record + 8]);

            // 縦
            is_fill_condition_9(target[x + 0],target[x + 9],target[x + 18],target[x + 27],target[x + 36],target[x + 45],target[x + 54],target[x + 63], target[x + 72]);

            // ボックス
            let segment_box = 3 * (x % 3) + 27 * (x / 3);
            is_fill_condition_9(target[segment_box + 0],target[segment_box + 1],target[segment_box + 2],target[segment_box + 9],target[segment_box + 10],target[segment_box + 11],target[segment_box + 18],target[segment_box + 19], target[segment_box + 20]);
        }
    }

    let end = start.elapsed();
    println!("{}{:03}s", end.as_secs(), end.subsec_nanos() / 1000000);
}

///
/// 指定された9つの要素が数独の条件を満たしていること
///
fn is_fill_condition_9(n1: char, n2: char, n3: char, n4: char, n5: char, n6: char, n7: char, n8: char, n9: char) -> bool {
    let mut target = vec![n1, n2, n3, n4, n5, n6, n7, n8, n9];

    target.sort();
    return target == ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
}

TypeScript(JavaScript) のコード

const main = (): void => {
    // 初期条件
    const target: Array = "864371259325849761971265843436192587198657432257483916689734125713528694542916378".split("");

    const start: Date = new Date();

    // ベンチマーク
    for (let bench: number = 0; bench < 100000; bench ++){
        for (let x: number = 0; x < 9; x++) {
            // 横
            const segmentRecord = x * 9;
            isFillCondition9(target[segmentRecord + 0],target[segmentRecord + 1],target[segmentRecord + 2],target[segmentRecord + 3],target[segmentRecord + 4],target[segmentRecord + 5],target[segmentRecord + 6],target[segmentRecord + 7], target[segmentRecord + 8]);

            // 縦
            isFillCondition9(target[x + 0],target[x + 9],target[x + 18],target[x + 27],target[x + 36],target[x + 45],target[x + 54],target[x + 63], target[x + 72]);

            // ブロック
            let segmentBox = 3 * (x % 3) + 27 * Math.floor(x / 3);
            isFillCondition9(target[segmentBox + 0],target[segmentBox + 1],target[segmentBox + 2],target[segmentBox + 9],target[segmentBox + 10],target[segmentBox + 11],target[segmentBox + 18],target[segmentBox + 19], target[segmentBox + 20]);
        }
    }

    const end: Date = new Date();
    console.log(`${end.getTime() - start.getTime()}ms`);
};

/**
 * 指定された9つの要素が数独の条件を満たしていること
 * 
 * @param n1
 * @param n2
 * @param n3
 * @param n4
 * @param n5
 * @param n6
 * @param n7
 * @param n8
 * @param n9
 */
const isFillCondition9 = (n1: string, n2: string, n3: string, n4: string, n5: string, n6: string, n7: string, n8: string, n9: string): boolean => {
    const target = [n1, n2, n3, n4, n5, n6, n7, n8, n9];
    
    target.sort();
    return target.toString() === ["1", "2", "3", "4", "5", "6", "7", "8", "9"].toString();
};

main();

結果

結果を確認したところ、 Rust より TypeScript(JavaScript) の方が早い結果を得られました。

$ cargo build
   Compiling rust_hello_world v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.80s
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/rust_hello_world`
7896ms

$ tsc main.ts
$ node main.js
3856ms

流石にそんなことは無いだろうと思って確認したところ、コンパイルと実行はデフォルトではデバッグモードで行われるため、そのままだと最適化されていないことが原因だそうです。
で、高速化するためには

--release

を使えば良いとのことでした。

$ cargo build --release
   Compiling rust_hello_world v0.1.0
    Finished release [optimized] target(s) in 0.57s
$ cargo run --release
    Finished release [optimized] target(s) in 0.04s
     Running `target/release/rust_hello_world`
0129ms

7896ms -> 129ms なので60倍くらい違いますね。
TypeScript(JavaScript) と比較しても 3856ms と 129ms なので30倍くらい違う。
これは早い。

参考

【fastify】Getting Started 新規作成 〜 サーバ立ち上げ

tl;dr

fastify の Getting Started を実装してみた

バージョン

node v11.2.0
yarn v1.10.1
fastify v1.13.x

内容

インストール

まずはプロジェクトの新規作成と fastify のインストールを行います。

$ yarn init # プロジェクトの新規作成
yarn init v1.10.1
question name (learning-fastify): 
question version (1.0.0): 
question description: 
question entry point (index.js): 
question repository url (git@github.com:kazumatsudo/learning-fastify.git): 
question author (kazumatsudo): 
question license (MIT): 
question private: 
success Saved package.json
✨  Done in 48.63s.

$ yarn add fastify # fastify のインストール
yarn add v1.10.1
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Saved lockfile.
warning Your current version of Yarn is out of date. The latest version is "1.12.3", while you're on "1.10.1".
info To upgrade, run the following command:
$ curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
success Saved 58 new dependencies.
info Direct dependencies
└─ fastify@1.13.0
info All dependencies
├─ @types/events@1.2.0
├─ @types/node@10.12.10
├─ @types/pino@4.16.1
├─ abstract-logging@1.0.0
├─ ajv@6.5.5
├─ ansi-styles@3.2.1
├─ avvio@5.9.0
├─ chalk@2.4.1
├─ color-convert@1.9.3
├─ color-name@1.1.3
├─ core-util-is@1.0.2
├─ debug@3.2.6
├─ deepmerge@2.2.1
├─ end-of-stream@1.4.1
├─ escape-string-regexp@1.0.5
├─ fast-decode-uri-component@1.0.0
├─ fast-deep-equal@2.0.1
├─ fast-json-parse@1.0.3
├─ fast-json-stable-stringify@2.0.0
├─ fast-json-stringify@1.9.2
├─ fast-safe-stringify@1.2.3
├─ fastify@1.13.0
├─ fastq@1.6.0
├─ find-my-way@1.15.4
├─ flatstr@1.0.8
├─ forwarded@0.1.2
├─ has-flag@3.0.0
├─ inherits@2.0.3
├─ ipaddr.js@1.8.0
├─ isarray@1.0.0
├─ json-schema-traverse@0.4.1
├─ light-my-request@3.1.0
├─ middie@3.2.0
├─ ms@2.1.1
├─ once@1.4.0
├─ path-to-regexp@2.4.0
├─ pino-std-serializers@2.3.0
├─ pino@4.17.6
├─ process-nextick-args@2.0.0
├─ proxy-addr@2.0.4
├─ pump@3.0.0
├─ punycode@2.1.1
├─ quick-format-unescaped@1.1.2
├─ readable-stream@3.0.6
├─ ret@0.1.15
├─ reusify@1.0.4
├─ safe-buffer@5.1.2
├─ safe-regex@1.1.0
├─ semver-store@0.3.0
├─ split2@2.2.0
├─ string_decoder@1.1.1
├─ supports-color@5.5.0
├─ through2@2.0.5
├─ tiny-lru@1.6.4
├─ uri-js@4.2.2
├─ util-deprecate@1.0.2
├─ wrappy@1.0.2
└─ xtend@4.0.1
✨  Done in 5.05s.

コーディング

fastify がインストールできたので、次にエントリファイルにサーバのソースコードを書きます。

// index.js
// logger を true にすると、ログが出力されるようになる
const fastify = require('fastify')({
    logger: true
});

// ルーティングの設定
fastify.get('/', function (request, reply) {
    reply.send({ hello: 'world' })
});

// ポート3000番で受ける
fastify.listen(3000, function (err, address) {
    if (err) {
        fastify.log.error(err);
        process.exit(1);
    }
    fastify.log.info(`server listening on ${address}`)
});

実行

あとは node で実行すればサーバが立ち上がります。

$ node index.js 
{"level":30,"time":1542936968069,"msg":"Server listening at http://127.0.0.1:3000","pid":16660,"hostname":"kazumatsudo.local","v":1}
{"level":30,"time":1542936968070,"msg":"server listening on http://127.0.0.1:3000","pid":16660,"hostname":"kazumatsudo.local","v":1}

ブラウザから http://127.0.0.1:3000 にアクセスして json が表示されれば成功です。

async/await

async/await によるコーディングも可能です。

// logger を true にすると、ログが出力されるようになる
const fastify = require('fastify')({
    logger: true
});

// ルーティングの設定
// reply.send を用いずに return で返すことも可能
fastify.get('/', async () => {
    return { hello: 'world' }
});

// ポート3000番で受ける
const start = async () => {
    try {
        await fastify.listen(3000);
    } catch (err) {
        fastify.log.error(err);
        process.exit(1);
    }
};

// 実行
start();

async/await で記述すると、 ルーティングで reply.send で JSON を送っていた箇所が、 return で返せるようになります。
async/await を用いない際は return で返すことができないので注意が必要です。

補足

fastify はロガーのライブラリに pino を用いています。
pino には、ターミナルへのログ出力を整形してくれる pino-pretty というモジュールがあります。
pino-pretty はパイプライン処理を行うだけで簡単に利用できます。


$ yarn add -D pino-pretty # pino-pretty のインストール
yarn add v1.10.1
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Saved lockfile.
success Saved 8 new dependencies.
info Direct dependencies
└─ pino-pretty@2.2.4
info All dependencies
├─ args@5.0.0
├─ camelcase@5.0.0
├─ dateformat@3.0.3
├─ jmespath@0.15.0
├─ leven@2.1.0
├─ mri@1.1.1
├─ pino-pretty@2.2.4
└─ split2@3.0.0
✨  Done in 0.92s.

$ node index.js # ログをそのまま出力
{"level":30,"time":1542936968069,"msg":"Server listening at http://127.0.0.1:3000","pid":16660,"hostname":"kazumatsudo.local","v":1}
{"level":30,"time":1542936968070,"msg":"server listening on http://127.0.0.1:3000","pid":16660,"hostname":"kazumatsudo.local","v":1}

$ node index.js | pino-pretty # ログを pino-pretty で整形し出力
[1542938115625] INFO (16828 on kazumatsudo.local): Server listening at http://127.0.0.1:3000
[1542938115626] INFO (16828 on kazumatsudo.local): server listening on http://127.0.0.1:3000

ハイライトの関係で実際のターミナル上での表示と差異がありますが、ログをそのまま出力した際は色は変わらず、 pino-pretty を用いると色分けされて表示されます。

ソースコード

参考