goccyでごっしー

技術的な話題をつらつらと書いたり書かなかったり

最近のCompiler::Parserについて

最近、Compiler::Parserの0.0.9をリリースしましたが、0.0.8から大分変更がありました。

大きく分けて2つあり、find/remove/walkメソッドのサポートと、大量のバグフィックスです

 

find/remove/walkメソッドのサポート

findに関してはCompiler::Parser::ASTやCompiler::Parser::Nodeを継承するすべてのモジュールに実装されており、remove/walkメソッドはCompiler::Parser::ASTに実装されています。

よくあるuse/requireしているモジュールの名前とexportされている関数の一覧を取得するようなコードは、findを使うと以下の様な感じに書けます

 

findの引数になっている'node'というkeyに対応して、Compiler::Parser::Nodeを継承しているモジュールの名前を指定すると、そのノードのリストを取得することができます。

同様に、find(type => 'Int')とすると、Compiler::Lexer::TokenのtypeがInt(Compiler::Lexer::Type::T_Int)であるtokenを持つノードのリストを取得します。

他にも、'kind'や'data'といったkeyを指定してノードを取得することもできます。

指定する名前のルールがわからないよ!という場合は、Compiler::Parser::Nodeの実装を見れば、サクッと分かっていただける気がしています。

(後日ちゃんとドキュメント化しようと思います)

 

removeは、対象となったノードをASTから削除することができます。

walkメソッドは、

* walk { my $node = $_ } $ast 

* $ast->walk(sub { my $node = shift; });

のような形式で使うことができ、AST上のノードを端から端まで探索したいときなどに使えます。

 

大量のバグフィックス

現在、Compiler::Parserのparse精度を高めるために、程よく大きく、多彩なシンタックスを使っていて、広く使われているものとして、Plackを対象に動作検証を行っています。

2014年4月現在で、Plack/Middleware以下のモジュールを除いた全てのモジュールのparseがうまくいくことを確認しており、以前よりも大分完成度が上がってきた気がしています。

(Middleware以下は、単にモジュールの数が多くて後回しになっています...)

parse結果はテストコードとして自動生成していて、テストコードを生成するタイミングで目視で整合性を確認しています。(うまくいっているかどうかの判断基準は難しいのですが、現状だと、ASTから正しいbytecodeが生成できるかどうかで判断しています)

目視確認せずに正確性を検証したいところではありますが、現状こんな感じでやっています。

 

テストコードがどうなっているかは、この辺から確認できます。

テストコードを見れば、生成されているASTのレイアウトがどんな感じなのかざっくりと分かっていただけるようになっています。

 

今後

Plack配下のモジュールを全てparseできた段階で0.1.0をリリースしようと思っています。また、併せてASTからソースコードを逆生成するdeparseメソッドを追加しようと考えています。このメソッドがあることによって、ASTが正しいかどうかが誰でも検証できるようになると思っています。(sourceA -> parse -> AST -> deparse -> sourceBでsourceAとsourceBが同じであれば、parse結果が概ね正しいと言える)

  

Compiler::Parserは、ロードマップや使い方に関して、もっとドキュメントを残していく必要性を感じているので、これから暇を見つけて投稿していこうと思います。

これからのアプリケーションデバッグについて思う

最近はiOSアプリケーションの開発がメインの業務になっていて、毎日延々とUIなどの開発をしていると、デバッグ環境がどうにかならんものかと思ってくる。

ある画面Cを開発しようと思ったときに、画面A->画面B->画面Cと画面遷移をして...といった作業が無駄すぎるからだ。画面A->画面BにいくためにボタンA、ボタンBを押して〜とかなってくると更に大変で、本来のデバッグ対象画面に辿りつくまでに、相当量の時間を無駄にしているように思える。

そこで、これを解決できたら結構面白いかなと考えて、学生のときに同じことを思って修論にしたアイディアをPerlMotionに実装したいなと思ったわけです。

 

ようはCheckPoint機能の実装をしようという話で、任意の状態をCheckPointとして記録しておき、アプリケーションを再起動するときに、一気に記録したCheckPointまでアプリケーションの状態を進めるという感じです。「任意の状態」というとなんだかこむずかしそうだけれども、ようは、「最初」から「任意の状態」にいたるまでのアプリケーションの状態を決定づける要素を全て保存していって、アプリケーション起動時に保存した情報を同じ順番で流しこめば、原理的に再現できるだろうと思うわけです。

 

アプリケーションの状態を決定づけるには、アプリケーションの振る舞いを変化させるものを追っていけばいいという前提で話を進めると、追うべき対象というのは、広義には「Event」なのかなと思っている。「Event」にはユーザーが画面をタップしただとか、HTTP通信だとかそういうものが入ってくると思っていて、言い換えれば、アプリケーションに対する外部からの干渉行為みたいなものだ。ここでいうEventをうまく定義できて、もれなく保存していけば、アプリケーションの状態は再現できると思う。

 

上のようなことを修論のときに書いて、自作エディタにCheckPointの設定機能をつけて、Qtアプリケーションの任意の状態を再現するというデモを作った。当時、なんとなく動いた感があったので、同じような機能をPerlMotionにくっつけたいなーと思っている次第です。

PerlMotionではiOS SDKが提供するAPIをラップする構成になっているので、イベントを保存する処理みたいなのを簡単にInjectできる。なので、これはいけるんじゃないかと思ってるわけです。

 

CheckPointがうまく動くようになれば、一度保存しさえすればアプリケーションの状態を過去・未来のいかなる状態にも一発で遷移させることができて、大分デバッグが快適になる気がしています。アプリケーションの時間軸を操作している気分になれて、なんだかタイムマシンみたいで良いですね。(コードを変えるとタイムパラドックスが起きて過去や未来の状態が変わるなんてことも起こりそうです)

 

というわけで、タイムマシンを作ろうと思っています。

 

 

 

 

より高速なテスト環境を実現するために

テストを書くことは良い事です。どんどん書いて品質を上げたいところではありますが、テストの数が数万〜数十万に及ぶようなプロジェクトでは、フルテストに要する時間が無視できない程膨れ上がってしまいます。

今回は、そんな増えすぎたテストコードの影響でフルテストに時間がかかっているプロジェクトのために、テスト高速化を実現するためのモジュールとその使い方について紹介したいと思います。

 

モジュールは、App::Ikarosというもので、現在version 0.02がリリースされています

使い方等はここにまとめてあるので(随時更新する予定)、この記事ではどのようなことができるモジュールなのかをざっと説明しようと思います。

 

まず、ざっくりとですがApp::Ikarosが提供する機能には以下の様なものがあります。

  • 多数のノードによる分散テスト実行
  • forkproveによるテスト実行の高速化
  • Devel::Coverによるカバレッジ計測
  • テストクラスタ最適化のためのProfiling機能
  • Jenkinsとの連携(JUnit形式のXML生成)
  • .proveを利用したテストクラスタの自動最適化
  • 失敗したテストの再実行

それぞれを順に見ていきます

 

1. 多数のノードによる分散テスト実行

これが本モジュールのメイン機能になります。

テスト実行したいノードを設定ファイルに書いておくと、App::Ikaros側で大体同じ時間でテストが終わるように、良い感じにクラスタリングして各ノードに振り分けます。テスト実行のログは、逐次振り分け元のmasterに送られ、テスト終了後には各slaveで生成された結果(JUnit形式のXMLファイル)を回収してJenkinsに渡します

 

2. forkproveによるテスト実行の高速化

forkproveはmiyagawaさん作のモジュールで、prove + モジュールのpreload機能がついたものです。システムが大きくなってくると、useすると芋づる式にほとんどのモジュールをuseしてしまうようなモジュールが存在するケースがあります。こういったモジュールを多数のテストで利用している場合、各テストのコンパイルに要する時間も馬鹿になりません。そこで、proveがテスト実行のためにfork + execをする前に、コンパイルに時間のかかるモジュールを読み込んでおくことで、モジュール読み込み時間をカットするというものです。

実際これは結構有効なケースがあって大変便利なのですが、先にモジュールを読み込んでしまうことによって生じる弊害も少なくありません。

例えばTest::MockObjectを利用しているテストがある場合、先にモジュールを読み込んでしまうとエラーになるケースがあったりします。

上記は正確に言うと回避策があるのでそこまで問題にはなりませんが、やんごとなき事情でforkproveの対象から除外したいテストもあるかと思います。

App::Ikarosでは、このあたりをいい感じに処理するために、「このテストクラスタはproveで、このテストクラスタはforkproveで処理する」というようなことを簡単に設定できるようになっています。

ですので、気軽にforkproveを使って高速化したいテストだけ高速化することができます。

 

3. Devel::Coverによるカバレッジ計測

Devel::Coverを有効にしてテストを走らせると、通常の何倍も時間を食います。

もともとが30分〜1時間以上かかる場合、テスト完了までに途方もない時間が必要となることが予想されます。

ですが、分散実行すればその時間も劇的に短くなるので、App::IkarosではDevel::Coverによるカバレッジ計測を簡単に行えるようサポートしています。

 

4. テストクラスタ最適化のためのProfiling機能

大量のテストからいくつかのテストクラスタを作ってそれを処理するようにすると、

テストを処理するノードによってテスト時間の偏りが生じがちです。

理想的には全てのテストクラスタが同じ時間で終わるように調整されるべきです。

また、ノードをどんどん追加していくと、理論的には追加したぶんだけ高速化されるはずですが、そうならないケースが存在します。一部の時間のかかるテストに全体のテスト終了時間が影響を受けるケースです。

App::Ikarosでは、テストクラスタ毎のテスト時間の偏りをなくしたり、一部の時間のかかるテストを効率的に見つけるためのProfiling機能を持っています。この記事を書いている時点では収集データをData::Dumperで出力するだけの貧弱なProfiling結果ですが、後のアップデートでちゃんとしたビジュアライザも用意したいなと思っています

 

5. Jenkinsとの連携(JUnit形式のXMLファイルの生成)

自分がCIツールにJenkinsを利用しているというのもあり、最終的な結果としてJUnit形式のXMLファイルを生成するようになっています。

Jenkins上で、「JUnit結果の集計」の項目で生成されたxmlファイルを指定すれば、簡単にJenkinsと連携させることができます。

 

6. .proveによるテストクラスタの自動最適化

App::Ikarosがデフォルトでやっているテストクラスタの最適化は.proveファイルを用います。.proveは、prove --state=saveとすることによって生成されるファイルで、中にはテスト実行時間等が記述されています。これをもとに、同じくらい時間のかかるテストがなるべく別クラスタになるよう調整され実行されます。

 

7. 失敗したテストの再実行

多数のノードを使って分散実行することになると、同時に走るテストの数が数十 ~ 数百になります。この規模になると、DBのデータ不整合等の問題も起きやすくなり、本来成功するはずのテストが失敗してしまったりします。そういったテストを最終的に失敗として報告するのは問題なので、最後の最後に失敗したテストを同一ホスト上で、並列度なしの状態で再度実行します。この結果失敗したテストがある場合は、本当に失敗したと判断し、最終結果に反映させます。

 

とまぁいろいろな機能がありまして、それぞれを簡単に利用できるように作っているつもりではいますが、まだまだ使いにくさなどあるかと思います。

そんなときは、pullreqなり@goccy54まで連絡していただければ幸いです。

 

今回、一切具体的な使い方について書かなかったので、後ほど気が向いたら

使い方も交えて書こうと思います。

 

 

 

 

 

 

 

 

 

 

 

 

YAPC::Asia 2013で発表してきました

はじめての投稿は、YAPC::Asia 2013で発表してきたことについて書くことにします。

YAPCに参加させていただくのは今年で2回目で、去年は「Perlと出会い、Perlを作る」というトークをさせていただきました。YAPC::Asia Tokyo 2012で発表してきました | mixi Engineers' Blog

 

今年は、去年作っていたgperlという処理系をベースに、処理系を構成する各処理(字句解析・構文解析・コード生成)をPerlのモジュールとして切り出して、それらを活用した応用例(Perlコードをブラウザ上やiOS上で動かす・静的解析で用いる)を示しました。

実際今みてみると、gperlから切り出した部分は字句解析部分のごく一部だけなのですが、Parserはこう作ると成功する・失敗するといったこととか、LLVMのコードを生成しようとか、そういうことを考える良いきっかけになってくれたことは確かで、そういう意味でも、処理系を作ってみてよかったなと思いました。プレゼンの最後にちょっと触れたのですが、切り出したモジュール郡の完成度が上がっていい感じになったら、gperlにbackportして、一気に処理系としての完成度を上げようと思っています。

で、ただ戻しても面白くないので、+αで静的型付けや型推論エンジンを実装してくっつけられればいいなぁと妄想していますが、いつになることやら....。

ただ、型推論エンジンはこれはこれでいろいろと用途がありそうなので、単体でモジュールとして切り出しても面白いんじゃないかなとは思っています。

 

ここまでがざっくりと発表内容の振り返りですが、一番反響があったのはPerlMotionに関する話ですかね。いろいろと記事を書いてくださったみなさまありがとうございます。実はYAPC直前まではちゃんと公開する気はなくて、ドキュメントも全然書いていないのですが、実際動かして試された方もいたりして、恐縮であります。

今後このブログ上で、PerlMotionに関する考え方や今のStatusなんかを公開していければいいかなぁと考えています。

 

他の方のトークで特に印象に残っているのは、kazeburoさんのやつと、myfinderさんのやつですかね。kazeburoさんのトークはとても勉強になって、ありがたやといった風合いで、myfinderさんの内容は、「あれ?これ自分がやったやつと同じだ」ってことでとても興味深く聞かせていただきました。

僕がフルテストを高速化したときには、Jenkinsを使っていたので、Jenkinsから分散コンピューティングを行うマスターに対してリクエストを送ったら、そいつから48台の子ノードに、全体テストの一部をそれぞれ送ってテストさせて、その結果をもらって最後にjunit_output.xmlを生成するっていうやつです。この話題に関しては、後ほど詳しくエントリを分けて書こうと思います。モジュールも書いてあるので。

ちなみに上記の話は ぼくとJenkinsおじさんの360日戦争 でさらっとですが触れています。

そういえば、質問でJenkinsの機能で分散ビルドできるのになんでそれ使わないのか的なのがあった気がしますが、Jenkinsが行う分散ビルドと、ここで言っている分散ビルドは少しコンテキストが違うと思ってまして、Jenkinsの機能では、フルテスト対象になってる全テストを分散して実行すること自体はできますが、その結果をひとつにして受け取るってのはできません(分けたテスト毎に結果が返ってくる)。こういう経緯もありつつ、テストを良い感じに同じ処理時間になるように各ノードに振り分けて、集めた結果をひとつにmergeして...といったことをやるモジュールを書いたという訳です。(他にはmiyagawaさんの書かれたforkproveを使えるようになっていたりという機能もあったりします)

 

話がそれてしまいましたが、そろそろまとめます。

 

今年のYAPC::Asiaへの関わり方は去年とは本当に別物でした。去年は右も左も分からない状態で参加して、日本にいらっしゃる著名なPerlハッカーの方たちもほとんど知らないというような状態でした。そんな状態からこの一年の間で、Perlコミュニティーにいらっしゃる著名な方たちと何度か話す機会を頂いて、徐々に名前を覚えてもらえるようになったことで、Perlコミュニティーにいることがとても楽しく、嬉しく思えるようになりました。

自分のトークが終わった後には、話したい!と言ってくださったReiniさんや、tokuhiromさんやgfxさん、miyagawaさん達とランチに行くことができてとても嬉しかったです。いろいろな話の中で、Compiler::*のネームスペースが広すぎで良くないというつっこみを受けてしまい、やっぱりそうだよなぁということでリネームを検討している次第であります。(Gompiler::* ????)

 

他にも、日吉のらすたは家系の中でもかなり美味い方だよなぁだったり、papixくん面白いなぁだったり、感想を言い出せばきりがないので、このへんにしようと思います。

 

最後に、とても楽しい機会をくださったlestrratさん、941さん、本当にありがとうございます!4年間お疲れさまです!