2015/06/18


Matz も驚く Crystal


Crystal

Language Goals Ruby-inspired syntax. Statically type-checked but without having to specify the type ...

http://crystal-lang.org/

ruby と殆ど同じシンタックスが通る様です。

# A very basic HTTP server
require "http/server"

server = HTTP::Server.new(8080do |request|
  HTTP::Response.ok "text/plain""Hello world!"
end

puts "Listening on http://0.0.0.0:8080"
server.listen

しかも native code が吐けるらしい。ならばとベンチマークを取ってみた。CRuby 側は以下の sinatra。CRuby は 2.2.0p0 を使用。

require "sinatra"

set :environment:production

get "/" do
  content_type 'text/plain'
  "Hello world!"
end
$ ab -k -c 10 -n 10000 http://localhost:8000

まずは sinatra

Server Hostname:        localhost
Server Port:            4567

Document Path:          /
Document Length:        12 bytes

Concurrency Level:      10
Time taken for tests:   8.502 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1620000 bytes
HTML transferred:       120000 bytes
Requests per second:    1176.24 [#/sec] (mean)
Time per request:       8.502 [ms] (mean)
Time per request:       0.850 [ms] (mean, across all concurrent requests)
Transfer rate:          186.08 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     1    8   2.6      8      66
Waiting:        1    8   2.6      8      66
Total:          1    8   2.6      8      67

Percentage of the requests served within a certain time (ms)
  50%      8
  66%      9
  75%      9
  80%     10
  90%     11
  95%     13
  98%     14
  99%     16
 100%     67 (longest request)

そして Crystal

追記: 以下はデバッグビルドの結果でした。その下に続けてリリースビルド時の結果を追記しています。

デバッグビルド

Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        12 bytes

Concurrency Level:      10
Time taken for tests:   0.633 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1010000 bytes
HTML transferred:       120000 bytes
Requests per second:    15802.98 [#/sec] (mean)
Time per request:       0.633 [ms] (mean)
Time per request:       0.063 [ms] (mean, across all concurrent requests)
Transfer rate:          1558.69 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       1
Processing:     0    1   0.2      1       3
Waiting:        0    1   0.2      1       3
Total:          0    1   0.2      1       3

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      1
  99%      1
 100%      3 (longest request)

リリースビルド

Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        12 bytes

Concurrency Level:      10
Time taken for tests:   0.295 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1010000 bytes
HTML transferred:       120000 bytes
Requests per second:    33888.54 [#/sec] (mean)
Time per request:       0.295 [ms] (mean)
Time per request:       0.030 [ms] (mean, across all concurrent requests)
Transfer rate:          3342.52 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       1
Processing:     0    0   0.1      0       1
Waiting:        0    0   0.1      0       1
Total:          0    0   0.1      0       1

Percentage of the requests served within a certain time (ms)
  50%      0
  66%      0
  75%      0
  80%      0
  90%      1
  95%      1
  98%      1
  99%      1
 100%      1 (longest request)

比べ物にならなかった。ただ、h2ohttp-server だとファイルをサーブしたとしても 28000req/sec から 30000req/sec は出る環境なので、Crystal のネイティブコードがめちゃくちゃ速いという訳ではない。

Posted at by



2015/06/09


golang 1.5 から、x86_64 のみですが -buildmode=c-shared というビルドオプションが足される事になりました。

これは、golang で共有ライブラリを生成する為のオプションで、例えば

package main

import (
    "C"
    "fmt"
)

var (
    c chan string
)

func init() {
    c = make(chan string)
    go func() {
        n := 1
        for {
            switch {
            case n%15 == 0:
                c <- "FizzBuzz"
            case n%3 == 0:
                c <- "Fizz"
            case n%5 == 0:
                c <- "Buzz"
            default:
                c <- fmt.Sprint(n)
            }
            n++
        }
    }()
}

//export fizzbuzz
func fizzbuzz(n int) *C.char {
    return C.CString(<-c)
}

func main() {
}

こういう golang のコードlibfizzbuzz.goがあったとして

$ go build -buildmode=c-shared -o libfizzbuzz.so libfizzbuzz.go

この様にビルドすると libfizzbuzz.so が出力されます。python を使えば

from ctypes import *
lib = CDLL("./libfizzbuzz.so")
lib.fizzbuzz.restype = c_char_p
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
1
2
Fizz
4
Buzz
Fizz

この様に関数を呼ぶ度に FizzBuzz が生成される関数も簡単に作れます。Vim には if_python という python 拡張もありますのでこれを使えば Vim プラグインの機能の一部として golang を使う事が出来る様になります。新しめの Vim であれば if_python はスレッドセーフなので

mattn/vim-go-fizzbuzz - GitHub
https://github.com/mattn/vim-go-fizzbuzz
let s:libdir = expand('<sfile>:p:h:h') . '/go'

if !filereadable(s:libdir . '/libfizzbuzz.so')
  exe "!cd " . s:libdir . " && make"
endif

function! fizzbuzz#start()
python<<EOS
import vim
import thread
import time
from ctypes import *

libfizzbuzz = CDLL(vim.eval('s:libdir') + '/libfizzbuzz.so')
libfizzbuzz.fizzbuzz.restype = c_char_p

def run():
  while True:
    vim.eval("fizzbuzz#on_fizzbuzz('%s')" % libfizzbuzz.fizzbuzz())
    time.sleep(1)

thread.start_new_thread(run, ())
EOS
endfunction

function! fizzbuzz#on_fizzbuzz(s)
  if mode() != 'n'
    return
  endif
  echo a:s
endfunction

1秒に1回、golang の goroutine で非同期にループしながら channel を経由して渡される FizzBuzz を、python のスレッドを介して Vim のメッセージが表示されるという、一見誰の役にも立ちそうにない事ながら未来感が溢れてきますね。どうしても python を書きたくない、でも非同期がやりたい!golang が書きたい!という人にはとても良い時代になってきました。もうすぐ、いろんなライブラリを golang だけで書ける日がやってきそうです。

ちなみに、libcallex-vim というプラグインを使えば

let fizzbuzz = libcallex#load("/home/mattn/dev/go-sandbox/libfizzbuzz.so")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
if_python も使わなくても(ただしCライブラリのビルドが必要) FizzBuzz 出来ますね。
Posted at by



2015/06/05


Vim で CtrlP を使っている人もそこそこいるかと思いますが、ファイル検索が終わり文字をタイプした時に行を絞り込む部分(マッチャーと言います)がデフォルトの状態だと Vim script を使って処理される為、ファイル数が多くなるとモッサリして来ます。これを解消する為に幾らかの人が頑張っています。

FelikZ/ctrlp-py-matcher - GitHub

Fast vim CtrlP matcher based on python

https://github.com/FelikZ/ctrlp-py-matcher
JazzCore/ctrlp-cmatcher - GitHub

CtrlP C matching extension

https://github.com/JazzCore/ctrlp-cmatcher
junegunn/fzf - GitHub

A command-line fuzzy finder written in Go

https://github.com/junegunn/fzf

どれも決め手に掛けるなーと思っていたのですが、そんな中に突如現れたのが cpsm です。

nixprime/cpsm - GitHub

A CtrlP matcher, specialized for paths.

https://github.com/nixprime/cpsm

README.md に書いてありますが、cpsm は python 拡張で実装されしかもスレッドで起動します。他のマッチャーでもそうですが文字列のファジーマッチングは意外に処理時間が必要になります。cpsm では使用可能なスレッド数を自動取得した後、ファイル一覧に対して並列に先取りして処理を行います。全ての一覧に対してマッチ結果が得られたらソートして vim 側に返しています。

cpsm/python_extension_main.cc at 00315bdd583f5083a90a6d8c6e38eef2f2d72070 · nixprime/cpsm · GitHub

CtrlP ではファイル一覧の作成には初回だけ時間が掛かりますが、以後は有限時間付きのキャッシュが読み込まれます。マッチャーの速度次第では目にも留まらない速度でファイルを選択出来る様になります。

以下は ruby22 のディレクトリ直下(総ファイル 24685個)を CtrlP で表示し、fluentd.gemspec を開くまでの操作です。

※キャッシュは作成済みです。

cpsm

はえぇ...。しばらく常用しようと思います。

尚、CtrlP ではファイルの一覧についても外部の機能を使う事が出来ますが、僕は files というツールを使っています。

mattn/files - GitHub

Fast file find

https://github.com/mattn/files

files は golang で書かれており、-a を付ける事で goroutine を使ってディレクトリ階層を下ってファイル一覧を出力します。よろしければこちらもお使い下さい。

CtrlP についてのご意見などあれば、現在 ctrlpvim/ctrlp.vim のメンテナをやってますので twitter 等でお声掛け下さい。

Posted at by