[C][IOCCC] The 24th IOCCC の結果が公開されました

C 言語のプログラムの汚さで競い合うプログラミングコンテスト、The 24th International Obfuscated C Code Contest (IOCCC 2015) のソースコードいまごろになって公開されました。

ref: http://www.ioccc.org/years.html#2015

既報の通り、自分は以下の 4 つの賞を貰いました。

順に紹介していきます。

Optcarrot: Ruby で書かれたファミコンエミュレータ

ウソみたいな本当の話。Rubyファミコンエミュレータを書いてみました。

気になる速度ですが、自分の環境では 20 fps ちょっと出ます。ファミコンは 60 fps なので、実速の 1/3 です。Ruby3x3 (Ruby 3 は Ruby 2 の 3 倍速い)という matz の宣言が実現すれば、実速が達成されることになりますね!

試してみたい人はこんなふうに実行してください。

$ gem install ffi
$ git clone http://github.com/mame/optcarrot.git
$ cd optcarrot
$ bin/optcarrot examples/Lan_Master.nes

SDL2 か SFML が適切にインストールされている必要があります。Debian/Ubuntu なら apt-get install libsdl2-dev で。

Ruby 処理系ベンチマークとして

optcarrot は --benchmark オプションを付けることで、ベンチマークモードになります。GUI 無しで 180 フレームを実行し、最後の 10 フレームの実行時間から fps を算出して終了します。

$ ruby bin/optcarrot --benchmark examples/Lan_Master.nes
fps: 34.25774482727466
checksum: 59662

いろんな Ruby 処理系で optcarrot --benchmark を走らせてみた結果がこちら。

MRI は 1.8 → 1.9 → 2.0 → 2.3 で確かに速まっているのがわかります。リリース時に "performance improvement" などと書かれているやつ、なかなか実感することはないですが、ウソじゃなかったんですね。MRI 高速化家の皆さんに敬礼。

JRuby や Rubinius は予想外に遅いですね。Topaz は「本家の 5 倍速?」ほどではないですが、健闘してます。*1追記:jruby-Xcompile.invokedynamic=true 使えばスタートアップ遅くなる代わりに処理速度が速くなる、とのことで、試したら MRI に匹敵しました。ちなみに jruby 9k のスナップショット版では MRI の 2 倍くらい出るようです。

IBM の OMR previewruby 2.2 ベースですが、2.2 より遅いんですよね。JIT の高速化より、プロファイルとコンパイルのオーバーヘッドの方が大きいようです。Ruby の場合、ボトルネックは評価器以外にあって、JIT で高速化する余地が相対的に少ないのかもしれません。

個人的に注目してほしいのは、mruby でも動いているところです *2 。Optcarrot はベンチマークプログラムを意識して書いたので、Ruby の基本的な機能(と自分が思うもの)しか使っておらず、外部ライブラリを一切使用しない *3 ので、移植が比較的容易です。なんと miniruby でも動くという、Ruby 処理系開発者に優しい仕様。

想定質問

なぜこんなものを作ったの?

Ruby3x3 を煽るため。OSS の開発を進めるには、開発者という馬を走らせるための「にんじん」が必要、というのは matz がたびたび言っていることです。そこで、その「にんじん」の 1 つとなればいいなと思って作りました。optcarrot は optimization carrot(最適化ニンジン)の略です。Ruby3x3 が達成されれば、開発者はファミコンゲームで遊べるというご褒美が得られます。

それから個人的に、遅い遅いと言われる Ruby の限界に挑戦してみたかったというのもあります。ファミコンエミュレータは 256 x 240 の画面を 60 fpsピクセル単位で描画する必要があります。しかし Ruby って、配列を 256 x 240 x 60 回更新するだけで 0.2 秒とか要するわけですよ。確かに遅い。残りの 0.8 秒で CPU シミュレーションとか音波合成とかしなきゃならない。無理ゲーです。この無理ゲーにどこまで応えられるか。最初にナイーブに書いたときは 3 fps とかだったんで、Ruby3x3 のストーリーに合うように 20 fps まで高速化しました。7 倍くらいの高速化は Ruby レベルの工夫次第でどうにでもなるということですね。

ごちゃごちゃ言ってるけど 60 fps でないんでしょ?

--opt を付ければ出るかも?

$ optcarrot --opt Lan_Master.nes

Optcarrot はベンチマークプログラムなので、なるべく普通で綺麗なコードを書くことにしたんですが、--opt を付けると綺麗さを犠牲にして高速化します。具体的には、まず自分自身のソースコードを読み込んで、メソッドインライン展開したり、簡単な部分計算したり、fastpath をこしらえたりして、コードクローンだらけの高速だけど最悪なコードを内部的に生成します。この処理は Ruby の得意とする文字列処理なんで、正規表現を駆使して適当にやってます。で、生成されたプログラム文字列を eval することでボトルネックの処理を置き換えます。これで自分のノートパソコンでは 60 fps を達成しました。めでたい。

まあ、こんなことは普通の Ruby プログラムではやるべきでないし、これをもって「Ruby で 60 fps 達成した!」と主張するのはちょっと何か違うような気もするので、おまけ機能です。60 fps 出たときはうれしかったけどね。前向きに言えば、MRI が今後何を最適化していくべきかを考える材料提供くらいにはなるかと。*4

--opt を含めたベンチマークはこちら。

MRI 以外速くならないですね。このモードでは巨大な case 文を内部生成するんですが、case 文をジャンプテーブル的に最適化するのって MRI だけぽいのでした。*5

どうやって高速化したの?

5 月の東京 RubyKaigi 11 で発表予定です。こうご期待。

今すぐ知りたい人はコード読めばいいと思います。

ref: http://github.com/mame/optcarrot/

余談

最適化っていうのは、「ここまで速くなればこれができる、速くならないとできない」というような、all or nothing な目標をもってやれば何とかなるものだなあと思いました。そのおかげで、まあ 3 fps から 60 fps までの 20 倍の高速化でも頑張れた。あと、コードの綺麗さと高速性の妥協点が決められる感じ。「少しでもいいから速くしたい」という煩悩は、漫然とコードを汚すばかり *6 で、最終的な効果が乏しいということになりがち。

*1:Topaz は最初 1 fps とかだったのですが、リファクタリングしたら 30 倍になりました。CPU のディスパッチを case 文から配列参照に置き換えたのが良かったみたい。JRuby や Rubinius も、たぶん同様にちょっとしたことで速くなるんだろうと思います。

*2:整数除算が整数になるようにするパッチを当ててます。

*3:GUI を表示する場合は、ruby-ffi 経由で SDL2 を使用します。

*4:Rubyボトルネックは昔と変わらずメソッド呼び出しのままなんだねーとか、インスタンス変数のアクセスは遅いなあとか。

*5:Topaz は生成コードが複雑すぎるせいか、コンパイルでエラーになるので動きませんでした。ただ、多分 Topaz も case 文が最適化されてないので、仮に --opt が動いても今すぐ MRI を超えることはないと思います。

*6:文字列リテラルに片っ端から .freeze つけるとかね。

Writing Qlock

うわーすごいなーと思ったので、パクリ インスパイアされてみました。Ruby プログラムで書き時計。

                         eval(T=%(eval(%(E=27.chr;Z=32.chr;$
                    ><<E+"[2J";K=->q{(q-q*(1-3844.0/q.abs2)**0.5)
                /2};I=->f,a,b,z,t=p{(a-b).abs>(f<1?1:1-(K[a]-c=K[b]).
             abs)?I[f,c=                                     (a+b)/2,b,I
           [f,a,c,z,t],t  :''''''''''''''''''''''''''''''':  ]:f<1?(x,y=b.
         rect;d="'."[y%2  :                               :  ];c=z[y/2+5];c[
       x+=58]=t||(c[x]==  :                               :  d||Z==c[x]?d:?:))
     :(puts(E+"[H"+$/+I[  :                               :  0,c,0,I[0,b,c,z.map
    (&:b)]]*$/);t||I[0,b  :         Writing Qlock         :  ,c,z,Z];sleep(0.01))
    ;z};s=(Z*25+"eval(T=  :                               :  %("+T+"))").lines.ma
   p{|l|l.chomp.ljust(90  :     (C) Yusuke Endoh 2016     :  )};loop{z=0i-31,[-1.0
   ,Z];h=10i-30;a="5?GUV  :                               :  XIIPCM.AAN&,HY/ZZZO7[
   &,HY3'CE<5SM5.OOJ+BBT  :                               :  3LV+A&YQ.STT[MF.KUVXP
   K+&[AOOJ'&5?GU57-B5SI  :                               :  51>E<5PCMF.K,DXPD+SM7
   .77'";i=92;"Q+3_.DW'`  :          inspired by          :  HAD,A11R`NK+HILJ/D'&F
    1.CG371|BE@355?5A7@@  :                               :  ??7|3-5-".scan(/../)
    {a.gsub!("%c"%i-=1,$  :    https://t.co/NSBi45Lj77    :  &)};Time.now.strftim
     e("%H:%M").bytes{|c  :                               :  |q=h;a.split(?&)[c-
       48].scan(/([0-8])  :                               :  |./){$1?q+=(n=$1.
         hex)%3-1+(n/3-1  :                               :  )*2i:z<<[q,$&]}
           ;h+=6};z<<a=3  :...............................:  1i-31;31.time
             s{|y|s[y/2+                                     5][58]=Z};g
                =z.map{|b,h|x,y=a.rect;g&&s[y/2+5][x+58]=g;I[1,a,b,s,
                    g];a,g=b,h};sleep(61-Time.now.sec);;;}).gsub(
                         /^(.{26}):.{32}/){$1}.split*"")##))

動画。

追記:動画は 5 の文字の書き順が間違ってた……。上のバージョンは修正済み。

『API デザインケーススタディ』の紹介

著者の田中哲さん (@tanaka_akr) から献本をいただきました! *1

本(電子版)をもらったのは RubyKaigi の前日の夜。翌日に TRICK 2015 の発表を控えていましたが、読み始めると面白すぎて、発表前までに読み終えてました。ちなみに献本とは別にジュンク堂 RubyKaigi 店で一冊買いました。(サインもろた)

自分が思った、読者層ごとの勝手な紹介を書いてみます。

普通の Ruby ユーザへの紹介

子プロセスを起動する Process#system 、#spawn 、open3.rb *2 あたりを使ったことがあるでしょうか? C の system 関数は使えない子として有名ですが、Ruby の Process#system たちはプロセス起動に関して「こんなことできたらいいな」と思うことが、たいていできます。しかも、簡単・ポータブルに。たとえばこの辺りの設計をしたのが、著者の akr さんです。

この本では、Ruby の I/O 、プロセス、時刻など、いろんな API 設計事例がオムニバス方式で説明されています。あくまでケーススタディの本なので、API 設計のやり方を体系的に手取り足取り教えてくれるわけではありませんが、節ごとに教訓が短くまとめられているので、Ruby でライブラリとか作るときに重要な示唆を得られるはず。汎用性の高そうな教訓の例をいくつか引用しておきます。

  • 「使いやすいというのは、実際に使う状況での使いやすさが重要ですから、実際に使う状況を調べることが重要です」(1.05節 0バイト読んだときに何を返す? - 用例を探して良い挙動を判断する)
  • プログラマが間違った方法を使ってしまうのを避け、正しい方法に誘導しています。」(2.03節 Socket.ip_address_list - 自ホストのIPアドレスを正しく簡単に得る)
  • プログラマの既に持っている知識を利用して学習しやすいものとするのが狙いです。」(5.04節 Integer#bit_lengthメソッド - 用途と前例を調べる)

他には、使いやすいライブラリ API デザイン(akr さんの RubyKaigi 2006 の発表) を読んで感銘を受けたら、読んで損はないと思います。

特に、Ruby に機能提案をしたいとか考えてる人は読むといいですよ。あわせて読みたいmatz を説得する方法(akr さんの RubyKaigi 2008 の発表)

また、「I/O」「ソケット」「プロセス」など、UNIX システムプログラミング寄りの内容が多めです。Ruby でそういうことをやりたい人にはとても参考になると思います。必読と言ってもいいかも?

歴戦の Ruby ユーザへの紹介

Ruby をよく使っている人なら何となく感じてると思うんですが、Ruby の組み込みクラスって玉石混淆なんですよね。なんというか、設計・実装の熟考度にムラがある *3 。その中で、ふと何か感心することのある部分は、たいてい akr さんが背後にいます。

そういう部分がどれほどの熟慮を重ねて設計されてきたか、この本を読めば分かります。それも、背景知識の説明を含めて、極めて理路整然とわかりやすく説明してくれます。Ruby マニアにとってこんな貴重な本はないです。

Ruby 以外のプログラミング言語ユーザへの紹介

Ruby を題材として API 設計のケーススタディを紹介する本なので、Ruby を知らない人にも読みやすいとは言いにくいですが、しかし他に類書ってあるんですかね *4 。言語やライブラリの API 設計について考えてみたい人は読むといいんじゃないでしょうか。ついでに Ruby を使ってみたくなるかもしれません。

追記akr さんが『APIデザインの極意 Java/NetBeansアーキテクト探究ノート』という類書を教えてくれました。こちらは互換性最優先という感じだそうです。

Ruby 以外のプログラミング言語の開発に携わっている人への紹介

あなたの言語の I/O 周り、プロセス周り、時刻周りについて、設計不備や機能不足を知ることができます。いや実際のところ、プログラミング言語のいわゆる泥臭い部分の API 設計についていろいろ書いてあるので、重宝するはず。

実用的なプログラミング言語を作りたいと思っている人への紹介

それがどれだけ遠い道のりなのかを痛感させてくれます。評価器とか GC とかだけでは実用的なプログラミング言語は作れないのです……。

プログラマではない人への紹介

世界各国の変態サマータイムについて豆知識が得られます。

Ruby 開発者への紹介

え、まだ読んでないの?

*1:自分と akr さんとは Ruby コミッタつながりです。

*2:1.8 のころの open3.rb を知っている人はネガティブな印象を持っているかもしれませんが、Ruby 1.9akr さんが大改修して現在は安心・安全の akr プロダクトになってます。詳しくは 3 章に書いてある。

*3:たとえば、カバレッジ API とかひどいですよね。カバレッジ測定を細かく制御できないとか、パスカバレッジへの拡張性を全く考えてないとか。

*4:強いて言えば C 言語の rationale とか?

RubyKaigi 2015 終了

2 年ぶりの RubyKaigi 参加でした。相変わらず楽しいですね。

自分たちが審査員やらせてもらった TRICK 2015 の発表については前の記事に書いたとおりですが、ひとつ重要なことを書き忘れてました。参考文献ですね。ああいうプログラミングをもっと見たいなーと思った人は、入門編として拙著を是非読んでみてください。

ジュンク堂 RubyKaigi 店ではサイン会をやらせて頂いて、完売できたということでホッとしました。買ってくれたみなさん、本当にありがとうございました。なお、書籍自体はもちろんまだまだ絶賛発売中なので、まだ買ってない人ももう一冊くらい欲しい人も買ってください。どっかで会えた時にはサインくらいしますので!

会議自体は虫食い的な参加だったので、個別の感想とかはやめときます。全体通して思ったのは、謝辞付きの発表が増えたなあ、ということでした。スポンサーが増えたということで、すばらしいことですね。

超久々に懇親会出れたのはよかったなあ。いろんな人に会えた。家族の人に感謝。

ということで、スタッフのみなさんもそれ以外のみなさんもありがとうございました。来年は京都ということで、TRICK の開催が危ぶまれます(笑) あれか、ぼくらも賞金(と審査員の出張滞在費)のスポンサーを募集すべきなのか。

TRICK 2015 落選作供養:そろばん時計

TRICK 2015 に投稿して落選したぼくのプログラムはこちら。

            $><<"\e[2J";z=
          32.chr;loop{s=(0..
        8).map{|i|[i>7??+:?|]*
      2*((i>7??-:i==2??=:z)*22)}
    j=1;Time.now.strftime("%H%M%S"
  ).bytes{|c|3.upto(9){|i|s[i%8][j+-
    j[2],3]=(i<8?c%5!=i%5:c<53!=i>
      8)?"<_>":z+?|+z};j+=4};s!=
        $s&&puts("\e[H"+s[8],*
          $s=s)}-_#TRICK2015
            #_Yusuke_Endoh

そろばんの玉のイメージです。実行すると、そろばんで時刻を教えてくれます。

+----------------------+
|<_><_>  <_> |   <_> | |
| |  |    | <_>   | <_>|
|======================|
|<_><_>  <_><_>  <_> | |
|<_> |   <_><_>   | <_>|
| | <_>   | <_>  <_><_>|
|<_><_>  <_><_>  <_><_>|
|<_><_>  <_> |   <_><_>|
+----------------------+

21:29:15 ですね。

落選理由は、正直ネタもイマイチなんですが、一番下に名前が書いてあること。ガイドラインに「自分の名前を入れんな」と書いたのに、自ら破ってしまいました。いや、なんで書いたのか全然思い出せない。
しかしまあ、名前書いてなかったとしても今回の入賞作と比べたら見劣りしますね。次回は全力を出して kinaba の 3 連覇を止めるようにがんばります。

TRICK 2015 結果発表

RubyKaigi 2015 で発表させていただきました。

入賞作品はこちら。

https://github.com/tric/trick2015

発表資料はこちら。

http://www.slideshare.net/mametter/trick2015-results

たくさんの投稿ありがとうございました!今回の入賞作は、発表会場でパッと見てうおおお!って言うのよりは、じっくり解析することでジワジワとスゲースゲーってなるのがたくさん入賞したので、ぜひ解析してみてください。

前回と比べて 2 倍程度の投稿があり、どれも面白いものばかりだったので、全部選べないのがとても残念でした。

他の審査員の方々もありがとうございました(ココ見てるかわかりませんが)。あと、投稿受け付けの事務局的なことからスライドのデザインまで、@hirekoke さんがやってくれました。感謝。


随時追記:観測した参加者の方々の声。後で見直すとき用のメモ。