Crystal - Ruby風文法を持ったコンパイル型言語

Erlang トレーニング 2013に申し込みを行った。Erlangは全く詳しくないので、トレーニングが非常に楽しみだ。新しいプログラミング言語や、新しいプログラミングパラダイムを学ぶことは楽しい。

さて、プログラミング言語にとって、ぱっと見の見た目は結構重要である。C言語系の見た目であるC++/Java/PHPなどは、C言語を知っていれば違和感を覚えにくい。

人は見た目が9割 (新潮新書)

人は見た目が9割 (新潮新書)

今回紹介するプログラミング言語の名前は、Crystalである。Ruby風の文法を持つプログラミング言語だ。つか、ほとんどRuby。アルゼンチンの会社であるmanas支援を受けて開発されている

Hello, world!はこちら。

puts "Hello, world!"

どうみてもRubyです(略)。ところが、以下のスクリプトはエラーが出る。

puts 'Hello, world!'

あくまで文法はRuby風、というわけ。

Crystalの実行速度

RPythonで書かれたRuby処理系であるTopazというものを以前のエントリで紹介した。そこで、MRIとTopazとの速度比較を行う一例として、セクシー素数を求めるコードを紹介した。引用すると、以下のようなコードである(Crystalのcommit 006fc13cで動作するように一部修正)。

def is_prime? n
  (2...n).all? { |i| n % i != 0 }
end

def sexy_primes n
  (9..n).map do |i|
    [i - 6, i]
  end.select do |i|
    i.all? { |j| is_prime? j }
  end
end

sexy_primes 100_000

上記セクシー素数を求めるコードを、手元のMacbook ProでRuby 2.0.0-p247とCrystalとで実行してみる。Crystalでは1.61秒。Ruby 2.0.0-p0では59.57秒。すごい速度差。それにはワケがあるわけで。

インストール方法

Ruby 1.9.3が入っているMac環境では、以下のようにしてCrystalを動作させることができる。2013年8月28日現在、いまだ.rvmrcを使っているのでrvm使いには警告が出る。また、Ruby 2.0.0-p247では動作しなかった。LLVMは3.3。

Linux環境では、libllvmが入るようなパッケージを入れればOK。

> brew install llvm --shared
> git clone git://github.com/manastech/crystal.git
> cd crystal
> bundle
> bin/crystal -e 'puts "Outsourcing!"' # ワンライナー
> bin/crystal -run hello.cr            # スクリプト実行

bin/crystalで実行してみると分かるが、体感として実行速度が遅いことに気づく。なぜだろうか。

Crystalの仕組み

LLVMをインストールしていたのでおわかりの方は多いと思うが、Crystalは、Ruby風の文法を解釈し、LLVM IRを生成し、それをネイティブコード化して実行している。ワンライナーの実行速度が遅いのは、そのせいだ。

よって、以下のように実行ファイルをコンパイルしておけば、動作速度は速くなる。

> bin/crystal hello.cr
> ./hello

生成されたLLVM IRをのぞき見ることができる。Hello, world!だとこんな感じ。

> bin/crystal -O3 -l1 hello.cr
; ModuleID = 'Crystal'

(中略)

@str2 = private constant [18 x i8] c"\0D\00\00\00Hello, world!\00"

define i1 @__crystal_main(i32 %argc, i8** %argv) {
alloca:
  br label %const

const:                                            ; preds = %alloca
  br label %"GC::objects_size"

entry:                                            ; preds = %"const_MatchData::EMPTY"
  %0 = load %MatchData** @"MatchData::EMPTY"
  store %MatchData* %0, %MatchData** @"$~"
  %1 = call i1 @"*puts:Nil"(%String.0* bitcast ([18 x i8]* @str2 to %String.0*))
  ret i1 %1

Crystalが目指すところ

Crystalは、ライブラリ経由でC言語の関数を呼び出しやすいように設計されている。例えばこんな感じで、sqlite3_libversion()関数を呼び出せる。

lib LibSQLite3("sqlite3")
  fun sqlite3_libversion : Char*
end

つまり、目指すところはC言語の代替物ということらしい。

例えば、samplesディレクトリにはHTTPサーバの例がある。

#!/usr/bin/env bin/crystal -run
require "socket"

server = TCPServer.new(8080)
puts "Listening on http://0.0.0.0:8080"

while true
  sock = server.accept
  while str = sock.gets
    if str == "\r\n"
      sock.print "HTTP/1.1 200 OK\r\n"
      sock.print "Content-Type: text/plain\r\n"
      sock.print "Content-Length: 12\r\n"
      sock.print "\r\n"
      sock.print "Hello world!"
      sock.flush
    end
  end
end

Crystalの適用領域として、HTTPサーバのようにそれなりにチューンしたいけど、Rubyの記述力も欲しいよね、といったニーズに特化しているものと予想する。

Crystalは現在セルフホスティング(CrystalでCrystalコンパイラを記述する)を目指しているようである。現在はruby-llvmを使っているはず。セルフホスティングされて、仕様も安定してきたら、それなりに高速なグルー言語としてお試ししてみたい。現在はTimeすらないし、 正規表現オブジェクトもない。正規表現オブジェクトはあった。facebookでツッコミを受けました。PCRE利用のようです。