5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Shell ScriptAdvent Calendar 2018

Day 2

シェルコマンドはどの言語で実装するのがいいのか?言語ごとの起動速度を比較してみた

Last updated at Posted at 2018-12-16

ファイル、ストリーム、コマンドライン引数、シグナルあたりを扱えれば、どんな言語でもシェルコマンドを実装できる反面、シェルスクリプトはコマンドを起動しまくる(=プロセスをforkしまくる)ので、シェルコマンドを実装するための言語は、プロセス起動のオーバーヘッドはできるだけ小さいほうが望ましいです。

そこで、各言語の起動時間比較を探してみたところ、Debianのメンテナの方がやっているのを見つけました。

26の処理系(Pythonは2系と3系で-Sオプションありなしがそれぞれあるので計4つ)で、Hello Worldを出力するコマンドを1000回起動して、かかった時間の平均をとっているようです。
おそらく今まで見た中でいちばん豪華なHello Worldです。

bdrungさんはこれらの結果を3つのカテゴリにまとめています

速い(1ms) まあまあ(5ms) 遅い(50ms)
Bash
C
C++
D (gdc)
Go (go)
Haskell
Lua
Pascal
Perl
Rust
Shell (dash)
CShell
Python 2 (with -S)
ZShell
C# (mcs)
Cython (Python 2)
Cython3 (Python 3)
PHP
Python 2
Python 3
Python 3 (with -S)
PyPy (Python 2)
Ruby
  • RaspberryPi 3で動かすと、bdrungさんのパソコン(Intel(R) Core(TM) i5-2400S CPU @ 2.50GHz, プログラマの使ってるマシンとしては普通かちょっと遅いくらい)のだいたい10倍くらいかかる
  • Pascalが0.08msで爆速ですね、C(0.26ms)より速い
  • Go, Haskell, Rust などの最近流行りのコンパイラ言語はいずれも「速い」カテゴリにはいっている
  • Ruby, Pythonなどが含まれる「遅い」カテゴリの言語は、RaspberryPi 3で実行すると500msec近く起動に時間がかかるようです

ここから言えることは、

  • シェルコマンドを実装する言語として、RubyやPythonを選択してしまうと、RaspberryPiのようなボードコンピュータで動かすのがつらいケースがでてくるかもしれない
  • 自社サービス向けのなんたらかんたらCLIみたいなのを実装するときは、クロスビルドのサポートが充実しているGoを選択しておくのが無難かもしれない
    • GoやRustやHaskellと言いたかったのだけどRustとHaskellを触ったことがない

手元でも動かしてみる

せっかくなので手元(MacBook Pro 13-inch, 2016, Two Thunderbolt 3 ports)で動かしてみます。(というか動かしてみないとタイトル詐欺になってしまう)
macOSで動かしたかったのですが、Debian前提のようなコードだったので、とりあえず素直にDockerで動かしてみます。

forkしてDockerfileを追加したものを置いておきました: https://github.com/miminashi/startup-time

実行

docker build -t startup-time ./
docker run -it startup-time

結果

Run on: Intel(R) Core(TM) i5-6360U CPU @ 2.00GHz | Debian GNU/Linux 9 (stretch) | 2018-12-16
Pascal (fpc 3.0.0+dfsg-11+deb9u1): 0.10 ms
C (gcc 6.3.0):            0.50 ms
Go (go go1.7.4):          0.51 ms
Shell (dash 0.5.8):       0.53 ms
Rust (rustc 1.24.1):      0.82 ms
Lua 5.2.4:                0.91 ms
C++ (g++ 6.3.0):          1.11 ms
Haskell (ghc 8.0.1):      1.13 ms
Bash 4.4.12(1):           1.17 ms
D (gdc 6.3.0):            1.17 ms
Perl 5.24.1:              1.29 ms
OCaml (ocamlc 4.02.3):    1.53 ms
ZShell 5.3.1:             1.78 ms
Python-S 2.7.13:          3.72 ms
Cython (cython 0.25.2):   9.30 ms
Python 2.7.13:            9.51 ms
Python3-S 3.5.3:          10.93 ms
PHP 7.0.33-0+deb9u1:      11.82 ms
C# (mcs 4.6.2.0):         15.16 ms
Cython3 (cython3 0.25.2): 17.47 ms
Python3 3.5.3:            17.60 ms
PyPy 5.6.0:               31.58 ms
Ruby 2.3.3p222:           48.97 ms
Go_GCC (gccgo 6.3.0):     55.42 ms
Java (javac 1.8.0_181):   55.59 ms
Scala (scalac 2.11.8):    305.10 ms
CShell 20110502-2.2+b1:   649.89 ms
  • 概ねbdrungさんの結果と同じ傾向でした
  • CShell(csh)だけがbdrungさんの結果に比べて200倍くらい遅くなっている、何故・・・

ちなみに、結果のソートはこんな感じでやってます。(シェルスクリプトアドベントカレンダーだし一応シェルスクリプトを書いた)

head -n 1 results/2018-12-16_1.txt &&
cat results/2018-12-16_1.txt |
sed '1d' |
sed 's/^..* \([0-9][0-9]*\.[0-9][0-9]*\) ms$/\1,&/' |
sort -t, -k 1n |
sed 's/^[0-9][0-9]*\.[0-9][0-9]*,//'

sort -n で小数をソートしてくれることを期待してますが、POSIX準拠の動作ではないので注意。

まとめ

  • 起動の速さ以外にも、クロスビルドのサポートやワンバイナリ化など、シェルコマンドをつくるに際して便利な機能があるので、現代においてシェルコマンドはやはりgoで書くのが無難なのかもしれない
    • というかgoの登場によって、作り置きしておいたシェルコマンドをシェルスクリプトでつなぎ合わせるという伝統的なUNIX Wayが再び現実的な選択肢になってきた
  • Rustもcargoがクロスビルドをサポートしているようなので、goと同じような使い方ができそう
  • 爆速のPascalや、Haskell, OCaml, Dなどもそのうち触ってみたい
  • NodeJSがなかったけど、論外ということなのだろうか?追加してみよう
    • きちんとベンチマークとったことはないけど、RaspberryPiでNodeJS動かすと起動に600msくらいかかっていた記憶がある
  • mrubyなんかもワンバイナリにビルドできるので、追加して比較してみたい
5
4
0

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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?