title: Raku(Perl6)のサーバーを使った高速実行 author: Kouki Fukuda, Shinji Kono profile: 並列信頼研 ## スクリプト言語の高速実行 - 現在多くのスクリプト言語はインタプリタ型言語であり, 実行時にインタプリタの立ち上げ, モジュールを読み込み, スクリプトの解釈, スクリプトの実行 といったような処理を担っている. - 頻繁にコードを書き換え実行するスクリプト言語では起動時間をできるだけ短くしたい. - その手法として同一ホスト内で終了せずに実行を続けるサーバープロセスを立ち上げ, このサーバープロセス上で立ち上げておいたコンパイラに実行するファイル名を転送し, サーバー上でコンパイルを行う手法を提案する - この提案手法に沿って『Abyss サーバー』を実装した. ## ## Raku と他言語の起動時間の比較 - Raku と他言語の起動時間の比較行なった. - perl5,ruby,raku,pythonでhelloworldを出力するプログラムを用いて行なった実行結果である.
Language Version Time Ratio
raku 2019.03.1 249 ms 62.25
perl5 v5.18.4 4 ms 1
python 2.7.10 13 ms 3.25
ruby 2.3.7p456 83 ms 20.75
Perl5 を基準とすると Raku はその62.25倍と非常に起動時間が遅いことがわかる. ## Rakuが遅い理由 - 通常 Ruby のようなスクリプト言語ではまず YARV などのプロセスVM が起動し,その後スクリプトを Byte code に変換して実行という手順を踏む. - Rakudo はインタプリタの起動時間及び, 全体的な処理時間が他のスクリプト言語と比較して低速である. - これは Rakudo 自体が Raku と NQP で書かれているため, MoarVMを起動し, Rakudo と NQP のByte codeを読み取り, Rakudoを起動し, その後スクリプトを読み取り, スクリプトの Byte code 変換というような手順で進むためである. - また Raku は実行する際に実行時の情報が必要であり, メソッドを実行する際に invoke が走ることも遅い原因である. - invoke はMoarVM の method 呼び出しのbyte codeです. ## Raku による Abyss Server の実装 - 提案手法に沿い『Abyss Server』を実装した. - Abyss Server はUnix domain socketを用いて送信した Raku スクリプトを実行するための Server である. - 下記の図は, Abyss Server を用いたスクリプト言語の実行手順です. ![](fig/Abyss.svg) ## 通常実行と提案手法の速度比較 - 今回は,提案手法での実行速度と通常実行での実行速度, この二つの速度の比較を行う - 題材として行うのはhelloworldを出力するだけのプログラムとフィボナッチ数列の例題である. ## 予測 - 前述した通り, Raku はコンパイラの起動に時間がかかっているため, 提案手法を用いることで起動時間分早く実行することができると予測する. ## 実行結果 - 通常実行 - 0.2695 sec - 提案手法 - 0.0238 sec - 提案手法は通常実行に比べて約10倍早い実行結果になった ## フィボナッチ数列の例題 - 通常実行 - 0.2128 sec - 提案手法 - 0.0415 sec - 先ほどと同様,提案手法は通常実行に比べて早い結果となり,約5倍早い実行結果になった ## Abyss Server側の実装 - Abyss Server は起動すると, まず自身にファイルパスを転送するためのソケットを生成し, その後ファイルを受け取るための待機ループに入る. - ファイルパスを受け取ると, ファイルを開き実行する. ``` sub close(int32) returns int32 is native { ... } sub dup(int32 $old) returns int32 is native { ... } sub dup2(int32 $new, int32 $old) returns int32 is native { ... } method readeval { my $listen = IO::Socket::Unix.new( :listen, :localhost, :localport(3333) ); my $backup = dup(1); say DateTime.now; loop { my $conn = $listen.accept; my $sock_msg; my $buf = $conn.recv(); $sock_msg = $buf; close(1); dup2($conn.native-descriptor(), 1); EVALFILE $sock_msg; dup2($backup, 1); close($backup); $conn.close; } $listen.close; } ``` ## Abyss Client側の実装 - ユーザーは Abyss Server を起動後,ファイルパスをサーバーに送信する. ``` my $conn = IO::Socket::Unix.new( :host, :port(3333) ); $conn.print: 'Absolute file path'; my $sock_msg; while my $buf = $conn.recv(:bin) { $sock_msg = $buf.decode; last; } say $sock_msg; ``` ## Raku のEVAL - Raku では EVAL 関数があり文字列を Raku のソースコード自身として評価できる - Raku では, EVAL は通常は使用できないようになっており, MONKEY-SEE-NO-EVAL という pragma を実行することで使うことができるようになる. ``` use MONKEY-SEE-NO-EVAL; EVAL "say { 5 + 5 }"; # OUTPUT: 10 ``` - EVALFILEはファイルパスを受け取ると, ファイルの中身をバイト文字列に変換し, それをEVALと同様に解釈する. ## Abyss Serverの利点 - Abyss Serverを用いて実行することで, サーバー上で事前に起動した Rakudo を再利用し, 投げられた Raku スクリプトの実行を行うため, Rakudo の起動時間を短縮できる. - 約10倍早くなる - 一度投げられたスクリプトのバイトコード, もしくは計算結果をキャッシュで保存しておき, 再度実行する際に, そのキャッシュを用いてコンパイル時間を省くような仕組みを入れやすいと考えられる. - 他の起動時間遅いスクリプト言語や, モジュールの読み込みが遅い言語などにも, 応用しやすいと考えられる. - 普通のスクリプト言語だと実行するたびにforkして実行しインタプリタの立ち上げという処理になるが, プロセス毎回起動しなくて済む ## Abyss Serverの欠点 - 現在 Abyss Server には 一度スクリプトを実行した後にサーバー内の環境をリセットする機能が存在しないため,スクリプトがサーバー内の環境に影響を及ぼした場合,通常実行と違う挙動をする危険性がある - 同時に二つ以上のタスクを与えられると実行順のスケジューリングができない - 異常に長いタスクが投げられた場合, 次のタスクが前のタスクが終わるまで実行ができない - 起動時のオプションが選択出来ない ## OS上でスクリプト言語を実行する方法の改善点 - OS上でスクリプト言語を実行する際の最適な方法として,提案手法のように事前に起動したコンパイラを再利用する方法は有効であると考える - またOS上でスクリプト言語を実行する際に, OS側で用意されてあるべきAPIとしては以下のようなものが挙げられる - 提案手法のように一度立ち上げられたインタプリタを立ち上げたままにする - 複数回投げられたスクリプトの実行結果もしくはbasic block を保存できる - 実行するスクリプトの周りにあるJsonファイルをあらかじめParseしておく ## まとめと今後の課題 - スクリプト言語 Raku の新たな実行方法の提案,及び提案手法に添って「Abyss Server」の実装を行なった. - Raku にUnix domain socket の実装を行なった. - Raku を用いて「Abyss Server」の実装を行なった - また今後今後の課題としては以下のようなものが挙げられる - 一度投げられたスクリプトをキャッシュで保存しておき,再度実行する際に,そのキャッシュを用いてコンパイル時間を省くような仕組み - 複数タスクが投げられた場合の処理の実装