満足するところまで作れたのでv.0.11.0をマージした。 Rustで作り直す前にはあった機能を一部実装していない*1があるが、一旦区切りをつけたかったのでマージした。 割り切り重要。Done is better than perfect. いや意味するところは違うと思うけど…。
前回の記事からの差分コミット:
- 0ce8bb6 Update README
- b2112cb Merge pull request #7 from buty4649/rust
- ee23604 Add mruby command
- 0abec1f Add rust-mruby
- cf89b78 Add cd to builtin command
- 05d20db Add -c option
- 577b95c Fix the behavior of Ctrl-C and Ctrl-D
- a3d7209 Add break and continue keyword
- 2a58c90 clippy
- f9d8637 Fix the problem of not saving automatically.
- 3834b4d Improve read command
- 47ae0b0 Add builtin command
- 16c648f Improve multiline command
- 89bad2b Clean up thread
- 05a45c2 Add support Comment line.
- 0b7b937 Excuted from a file
- d0f9b77 Split context from Executoer and a few special vars
- e119e73 Fix error code
- a55d480 Enable clippy
clippyを有効化
VSCodeのRust extensionを使ってコーディングしていて、保存するときにフォーマッティングしてくれていたのでclippyが効いていると思いこんでいたのだが実はrustfmtが実行されているだけで、clippyが有効になっていなかった。 オプトインになっているようなのでVSCodeの設定から有効にし、指摘されたところを修正した -> a55d480。 なるほど〜ということが多いので有効にしておくとすっきりしたコードがかけると思うのでおすすめ。
mrubyを内蔵
reddish-shellではmrubyを内蔵して、Rubyコードが実行できることもコンセプトとしている。
Rust化してもそのコンセプトを残したかったのでRustからmrubyを呼び出せるようにした。
幸い先行実装(crate)があるのとRustでFFIするのが楽だった*2。
しかし、今回はすでにあるcrateは使わず自前で実装した -> buty4649/rust-mruby: mruby bindings for Rust.
すでにあるcrateはbuild_config.rbが使えなかったり、使用しているmrubyが古かったりして絶妙マッチしなかったのが理由。
rust-mrubyはまだ大したことはできないが、build_config.rsを作って MRuby::new().exec_from_string("puts 'hello world', &[], None)
とかすると動くので割と満足している。
mrubyでスクリプトを実行すると、呼び出し元から安全にmruby VMを停止させる方法がないためにCtrl-Cが聞かずに無限ループしてしまう問題がある。 今後実装したい機能として、sourceコマンドでRubyスクリプトを呼び出すことができシェルを初期化できるということをやりたいのだが、初期化スクリプトの中で無限ループしてしまうと外からkill -9なりするしかなくなるという…。 色々考えたりコードを修正してみたのだが安全に停止する方法がないので、諦めてforkすることにした。 終了するときは、mruby VMごとプロセスを破棄するという手法を取ることにした。 できないことを無理にやろうとしても辛いからね…。 今のところはこれで困っていないので、今後困ったら再度考えることにする。
実行速度の改善
Rustで再実装しようと思ったきっかけの1つに実行速度の遅さがあった。 再実装前(v0.10.0)は、bashと同じスクリプトを実行させると6倍以上遅いという結果になっていた。 今回の再実装により、bashより2.6倍程度遅いくらいまでには高速化されて満足している。 というかbashが早すぎる…。
ベンチスクリプト↓ builtinコマンドの実装による速度差をなくすためにOS標準のコマンドを使用している。
❯ cat bench.sh #!/bin/bash TEST=0 while /usr/bin/[ $TEST -ne 100 ]; do TEST=`/usr/bin/expr $TEST + 1` done
使用するreddish-shellはv0.10.0(mruby+C実装)とv0.11.0(Rust実装)
❯ ./v0.10.0/reddish --version reddish: 0.10.0 ❯ ./v0.11.0/reddish --version reddish 0.11.0
ベンチマークはhyperfineを使った。
❯ hyperfine --warmup 3 'bash bench.sh' './v0.10.0/reddish bench.sh' './v0.11.0/reddish bench.sh' Benchmark 1: bash bench.sh Time (mean ± σ): 79.7 ms ± 2.3 ms [User: 68.9 ms, System: 14.3 ms] Range (min … max): 76.4 ms … 85.0 ms 37 runs Benchmark 2: ./v0.10.0/reddish bench.sh Time (mean ± σ): 491.1 ms ± 10.9 ms [User: 451.8 ms, System: 43.2 ms] Range (min … max): 481.2 ms … 512.1 ms 10 runs Benchmark 3: ./v0.11.0/reddish bench.sh Time (mean ± σ): 211.0 ms ± 3.2 ms [User: 191.1 ms, System: 30.9 ms] Range (min … max): 206.0 ms … 217.1 ms 14 runs Summary 'bash bench.sh' ran 2.65 ± 0.09 times faster than './v0.11.0/reddish bench.sh' 6.16 ± 0.23 times faster than './v0.10.0/reddish bench.sh'
今後の予定
2ヶ月くらいかけて今まで作ったmruby+Cでの実装をRustで再実装できたのは満足している。 Rustも最初はとっつきにくいかと思ったけど、自転車本のおかげですんなりRustで開発できるようになってよかった。
これでreddish-shellの開発は終わり!というわけではなく、今まで登ってきた道をイチからやり直して戻ってきただけなので、「オレはようやくのぼりはじめたばかりだからな このはてしなく遠い自作シェル道をよ…」という感じ。 まずは、再実装前にあった機能を実装するのと、テストコードの追加をしたいと思う。 パーサーを作っているときにはテストコードを追加していたのだが、シェルの実行エンジンを作り始めてからシステムコールなど外部に依存する部分が増えテストが書きづらくなったのもありサボってしまった・・・。 言い訳はよくないのでガっとテストコードを追加してGitHub ActionsでCIするところまで設定したい。