« May 2007 | Main | July 2007 »

Friday, June 22, 2007

Erlang で静的解析チェックをする

Erlang R11B-5 から,dialyzer が強化されて,かなりのチェックが可能になった♪
以前は型チェックと書いていたけれども,実際のところは,型チェックより幅広い範囲でチェック可能なため,静的解析と言う方が正しい感じなので,訂正.

普段の make 時に,必ず dialyzer も実行するようにすると,実行前にエラーを検出できて,デバッグ効率が上がると思う.

Erlang Tips に静的解析によるチェックの項目を追加した.

dialyzer による静的解析では,型チェックだけではなく,case の分岐に不要なものが混じっていないか,永久ループして抜けない関数がないか,などもチェックしてくれる.

また,typer を使うと,各関数がどのような入出力をするのかが,簡単に確認可能.
複数のcase文でタプルを返すような場合,タプルが返される,というだけではなく,それぞれのcaseでどのような構造のタプルが返されるかまで追いかけてくれるので,かなり便利に使えると思う.

Erlang Tips のサンプルで言うと,

%% @typer_spec get_record2/0 :: (() -> {'fail','ng'} | {'success',123})

というような解析結果になったりする.

Erlangの標準ライブラリは,各関数の型情報(?)がインストール時に処理されて用意されているので,標準ライブラリを呼び出すときに,引数の型を間違えた・・・というような場合も検出してくれる.

たとえば,リストを受け取る lists:reverse にタプルを渡そうとすると,

typetest2.erl:7: The call lists:reverse(A::{1,2,3}) will fail since the signature is lists:reverse/1 :: (([any()]) -> [any()])

というように検出してくれる.

| | Comments (2) | TrackBack (0)

Saturday, June 16, 2007

Erlang Tips 更新

Erlang Tipsを更新.

処理の高速化について,とりあえず思いついたところを追記.
(他に何か良いTipsがあれば教えてください...)

プロセスディクショナリ・ets・dets に関する比較などを追加.

dets・Mnesiaに関してはサイズ制限がなんとかなれば,もっと使いやすいと思うのだけども.
Web系をやる場合は,DBは外部DB(MySQL InnoDB とか)を使って,キャッシュをetsに入れると良さそう.
Mnesiaは耐障害性を確保するのがなかなか難しそうなので...

| | Comments (0) | TrackBack (0)

Friday, June 15, 2007

Erlang で echo server

gen_tcp の練習で echo server 書いてみたより


誰か添削してくれないかなぁ…

ということなので,わたしならこう書く,というものを以下に.
添削というほどのことは無理ですが,自分の練習を兼ねて…(^^;

-module(echoserver2). -export([start/0, handle_connection/1, recv_loop/1]).

start() ->
  {ok, ListenSocket} = gen_tcp:listen(8080,
    [{active, false}, binary, {packet, line}, {reuseaddr, true}]),
  accept(ListenSocket).

accept(ListenSocket) ->
  {ok, Socket} = gen_tcp:accept(ListenSocket),
  spawn(?MODULE, handle_connection, [Socket]),
  accept(ListenSocket).

handle_connection(Socket) ->
  gen_tcp:send(Socket, <<"hello\r\n">>),
  recv_loop(Socket).

recv_loop(Socket) ->
  case gen_tcp:recv(Socket, 0) of
    {ok, B} ->
      case B of
        <<"bye\r\n">> ->
          gen_tcp:send(Socket, <<"cya\r\n">>),
          gen_tcp:close(Socket);
        Other ->
          gen_tcp:send(Socket, Other),
          recv_loop(Socket)
      end;
    {error, closed} ->
      ok
  end.


※見た目のために全角スペース入り.

以下修正点など.

・そもそもの仕様がよくわからないのだけど,echoサーバなら,1行入れるたびに応答が返ってくるもの,という想定にした.
 元のコードでは,手元の環境では何も入力せずに改行をした場合に,それまでの応答が返ってきていた.
 また,byeを入れても終了しなかった.
 (たぶんパケットの切れ目が環境によって異なるためと思う)

・listen では,reuseaddr しておいた方が何かと良いと思うので,追加.
 reuseaddr していないと,一度止めて再度起動したときに,ポートが使用中で listen に失敗することがある.

・1行単位での入出力さえあれば十分と思うので,listen には {packet, line} オプションを渡した.
 これで,入力は1行単位になるので,recv_loop の受信処理でパケット境界を気にしなくて済む.
 ※ただし,1行が長すぎると途中で切れるので,その点では注意が必要

・spawnするときのモジュール名は ?MODULE マクロを使うように.
 後でモジュールの名前を変える時に余計な手間を省くことが出来る.

| | Comments (1) | TrackBack (2)

Erlang の smart_exception で例外が変になる

smart_exception をデフォルトで使うようにしていたのだけど,どうも例外がおかしくなってしまう模様.
これじゃ使えないじゃない.しょんぼり.

とりあえず家に帰ったらTipsからはいったん消しておこう.
これははまる.

以下のようなコードで確認できる.

-module(exception). -export([test/0]).

test() ->
  try
    test2()
  catch
    Ex -> io:format("Catch ~p~n", [Ex])
  end.

test2() ->
  throw(exception),
  io:format("throwed exception~n").

これを smart_exception 有り無しで実行すると・・・

$ erlc exception.erl && erl -noshell -run exception test -run init stop Catch exception

$ erlc +'{parse_transform, smart_exceptions}' exception.erl && erl -noshell -run exception test -run init stop
throwed exception

こうなってしまい,smart_exception を使うと,throwが無視されてしまう...

----

追記.

氷魚にゃさんが原因を見つけてくれた.

smart_exceptions.erl の

%% define r10 means 'try' is used in the generated code rather
%% than 'catch'. Note that try is _handled_ by default.
%% -define(r10, true).

となっているところの -define をコメントアウトして

-define(r10, true).

とすると直る模様.

「R10B compatibility is weak, not very tested」と書いてあるところが
多少不安だけども(^^;
(すでにR11Bだし)

----

Erlang Tipsの方も修正しました.

インストールされた方は,お手数ですがもう1回入れ直してください.
tar.gzのアーカイブも新しいものに差し替えました.

| | Comments (0) | TrackBack (0)

Thursday, June 14, 2007

Erlang R11B-5 リリース

Erlang R11B-5 がリリースされた.

詳細はリリースノートを参照.
今回のリリースで一番うれしいと思うのが,dialyzerの改良.
まだデフォルトでは無効になっているようだけど,オプションを指定すれば,関数の呼び出しの型チェックが行えるようになる.

R11B-4 までは,関数の型を推測することはできたものの,エラーチェックには生かせていなかった.
R11B-5 では,dialyzer に --succ_typeing オプションを渡すことで,エラーチェックをすることが出来るようになった.


-module(test).
-export([test_error/0, test_ok/0]).

test_error() ->
test(1).

test_ok() ->
test([1]).

test(N) when is_list(N) ->
ok.


というコードを想定する.

test(N) はリストしか受け取らないので,それ以外が渡された場合は function_cause エラーが起きてしまう.

今までは


$ dialyzer -c test.erl
Checking whether the initial PLT exists and is up-to-date... yes
Proceeding with analysis... done (passed successfully)

のようにエラーチェックが出来なかったのだけど,R11B-5 では以下のようにエラーチェックが可能になった.

$ dialyzer --succ_typings -c test.erl
Checking whether the initial PLT exists and is up-to-date... yes
Proceeding with analysis...
test.erl:5: Function test_error/0 has no local return
test.erl:6: The call test:test(1) will fail since the signature is test:test/1 :: (([1,...]) -> 'ok')
done (warnings were emitted)

ちなみに,型の推測は typer コマンドで出来る.

$ typer test.erl
Processing file: "test.erl"
Saved as: "./typer_ann/test.ann.erl"
$ cat ./typer_ann/test.ann.erl

-module(test).
-export([test_error/0, test_ok/0]).

%% @typer_spec test_error/0 :: (() -> none())
test_error() ->
test(1).

%% @typer_spec test_ok/0 :: (() -> 'ok')
test_ok() ->
test([1]).

%% @typer_spec test/1 :: (([1,...]) -> 'ok')
test(N) when is_list(N) ->
ok.


make時に自動でチェックするようにすると便利そう.

| | Comments (0) | TrackBack (0)

Wednesday, June 13, 2007

Erlangでのプロファイリング

Erlang Tipsに,Erlang での処理時間の計測方法を追加しました.
timerを使った基本のやり方と,eprof/fprof を使うやり方の両方を書いてあります.

fprofは関数の呼び出し関係を追いかけるせいか,非常に重く,少し処理時間がかかるコードになると使い物にならなくなります.
ちょっとしたベンチなら,timer:tc か timer:now_diff,コードの重い部分を探したいなら eprof が良い感じです.

| | Comments (0) | TrackBack (0)

Tuesday, June 12, 2007

Erlangのレコードを書きやすく

Erlangのレコード(Cでいう構造体みたいなもの)は,いまいち使いづらい.

定義

-record(name, { sei, mei }).
-record(user, { userid, name }).

作成

User = #user{ userid = 1, name = #name{ sei = "sawatari", mei = "mikage"}}.

一部変更

IdChangedUser = User#user{ userid = 2 }.
NameChangedUser = User#user{ name = (User#user.name)#name { mei = "mika" }}.

参照

Mei = (User#user.name)#name.mei.

参照は

Mei = User#user.name#name.mei.

とかけそうだけど,こう書くとエラーになってしまう.

#演算子はレコード以外のところでは使われていないので,演算子の優先度をちょっといじれば直せそう・・・
ということで氷魚にゃさんにパッチを書いてもらった.

[erlang-patches] patch for better record syntax.

これで少しは使いやすく.

と,今気づいたけど

NameChangedUser = User#user{ name = User#user.name#name { mei = "mika" }}.

と書くのはまだエラーになってしまう模様...


レコードは全般的に使いにくいと感じてしまうんだけど,もともと破壊的な変更が出来ないので,一部を変更するときは全体をコピーする必要がある.というところに原因があるのかな?
これは関数型言語ではわりと仕方ないこと?
Haskellでも,結構似たような状況のようだし...

----

追記:

NameChangedUser = User#user{ name = User#user.name#name { mei = "mika" }}.

にも対応したパッチがリリースされました

これで()をネストしなくて済むようになります.

#はErlangではレコード以外には整数表現(16#ffff)くらいにしか使われてないようで,今のところ他に副作用も無い感じ.
本家にもこの修正反映されるといいけど...どうだろう(^^;

| | Comments (1) | TrackBack (1)

情報処理

うーん,残念.

テクニカルエンジニア(システム管理)試験

午前試験のスコアは,670 点です。
午後I試験のスコアは,640 点です。
午後II試験の評価ランクは,B です。

午前&午後I は予想以上に取れていた模様.
やはり時間が無い中とりあえず字数だけ稼いだのがまずかったのか(^^;
漢字が書けないので,言い方を変えたり問題文から漢字を探したりして,その辺で余計な時間を食ったのが敗因かなー...
次回は少し漢字を練習してチャレンジしよう...
なんか情報処理の試験というより,漢字の試験になっている気がするけども(^^;

| | Comments (0) | TrackBack (0)

Monday, June 11, 2007

Erlang Tips 更新

Erlang Tips 更新.
Erlang Tips

+A オプションに関する説明を追加して,ベンチマーク結果を乗せた.
サーバなどを書いていて,確実にデータを保存するために file:sync を使ったりする場合は,かなり効果がある模様.

async thread pool を使う処理の一覧とかがあればいいんだけど,見つけられなかった.
ソースをざっと見た限りでは,ドライバの async_ready のところにハンドラが登録されていれば async になるような感じだったけど,fileを扱うドライバ以外で使われていない感じだった.
かなり効果は限定的な範囲なのかもしれない...


それと,perlre が改良されたので,それに関する記述も追加.

| | Comments (0) | TrackBack (0)

Sunday, June 10, 2007

Erlang Tips

ブログに書いたネタをまとめておこうと,ページを作成.

Erlang Tips

この後は,前にも書いたスケジューラスレッド・async thread poolの話とか,高速化についてとかを,追記していこうかなと...

| | Comments (0) | TrackBack (0)

Friday, June 08, 2007

Erlangで行番号付きでエラーを出力する方法

Erlangは,通常エラーが起きたときにスタックトレースを出してくれるのだけど,行番号の情報が含まれない.
少し長めの関数を書いていたりすると,関数の中のどこでエラーが出たのかなかなかわからないことがある.

そこで,エラーが発生した行番号を出せないかと調べていたら,FAQにしっかり書いてあった.
5.19. ...find out which line my Erlang program crashed at?

以下のようにインストールしておくと便利だと思う.

まず,smart_exceptionsからソースをダウンロード.
ディレクトリ構造はそのままダウンロードしてくる.
で,srcの中のファイルをコンパイルしてebinの中に入れておく.
(Makefile自体はjungerlのサポートファイルが必要なので手作業でコンパイルした方が早いと思う)
その後,smart_exceptions を /usr/local/lib/erlang/lib の下などに置けばOK.

置く場所は環境によって異なるけれども,

mikage@pepper:/usr/local/lib/erlang/lib> erl -noshell -eval 'io:format("~p~n", [code:lib_dir("inets")]),halt().'
"/usr/local/lib/erlang/lib/inets-4.7.11"

とかやって,表示されたディレクトリと同じような場所に置けばOK.

めんどくさい人は,コンパイル済みのを
http://pepper.sherry.jp/mikage/smart_exceptions.tar.gz
に置いておいたので,コレを持って行って解凍してください.


使い方は次の通り.

erlc +'{parse_transform, smart_exceptions}' test.erl

のようにオプションをつけてコンパイル.

あとは普通に実行すれば,

1> test:test().
** exited: {{test,test,0},{line,10},match,[5]} **

というようにエラーが表示される.

| | Comments (0) | TrackBack (0)

Thursday, June 07, 2007

ErlangでComet設置方法

うまく動かないと問い合わせがあり,設置方法をちゃんと書いてないことに気づく.
というわけで,試しに設置してみる方は,以下のように yaws を設定してください.

・index.yaws,chat_temp.html は yaws.conf の
 docroot で指定した場所の直下においてください.
 .yawsファイルはコンパイル等は不要です.

・chat.erl は,yaws.conf の ebin_dir で指定した
 場所の直下において,そのディレクトリでコンパイルしてください.
 ebin_dir の中に,chat.beam ファイルが必要です.
 (コンパイル後,ソースは消しても大丈夫です)

・yaws.conf の runmod で,
 runmod=chat
 と指定してください.
 この指定をすると,ebin_dir の指定モジュールの
 start/0 が yaws 起動時に実行されます.

また,ErlangでCometの記事にも書いてありますが,yaws本体に
パッチをあてておかないと,PIPELININGの相性問題が生じます.

yawsのソースの yaws.erl にサーバ名称を返す部分があるので,
yaws.erl: ["Server: Microsoft-IIS/5.0; Yaws/", yaws_generated:version(), " Yet Another Web Server\r\n" |
というように書き換えてコンパイルし直します.

| | Comments (0) | TrackBack (0)

ErlangでPerl正規表現

ErlangからPerl正規表現を使うモジュールをMLにも投げてもらいました.

[erlang-questions] using Perl regexp from erlang

evalも使えるようになっているので,Perl使っている人がErlangを使い始めるときに,便利に使えるかもしれません.
ただし,smpでも同時に1個しかPerl側の処理が走らないので,そこは注意が必要かも.

| | Comments (0) | TrackBack (0)

Wednesday, June 06, 2007

Image::ExifToolバージョンアップの罠

新しいサーバへの移転ついでに,モジュールのバージョンも最新のものに差し替えているのだけど,Image::ExifToolではまった.

最近のバージョンでUTF-8に対応したらしく,処理が色々変わっていた.
ちゃんとUTF-8になるならいいんだけど,IPTCタグに日本語を入れた場合には未対応だった模様.
SJISコードで入るのだけど,それを変に変換して文字化けしたデータしか返ってこない.

Charset => 'Latin' オプションを指定すると,余計な変換をしなくなるようで,元のSJISバイト列が取り出せた.

便利なモジュールだけど,こういう変更はきつい...
CharsetConvert => 'none' みたいなわかりやすいオプションだったらまだ良いんだけど,Latin指定で変換されないことに気づくまでに時間がかかってしまった.

| | Comments (2) | TrackBack (0)

ErlangでComet

ErlangでCometしてみた.
(iframe方式は結局だめそうなので,Cometに)

Comet チャットサンプル

実装はきわめて手抜きです.あくまでサンプルとして.

ソース:
chat.erl (22行.etsテーブルの初期化)
chat.yaws (72行.サーバ側の実装)
index.html (52行.HTML+JS)
※別途jqueryが必要

こんだけ短いソースでも,ちゃんとCometできるし,C10Kにも対応できる.(問題はいろいろあるコードだけど)
いろいろちゃんと実装したとしても,かなりシンプルに仕上がると思う.

Comet の正しい使い方で紹介されているプログラミングモデルのどれにも当てはまらない新しい方式,といえるかも.

Cometのために特殊な実装をする必要もないし,I/O多重化など実装がめんどくさい手法を使う必要もない.
普通に各リクエストの処理を書いて,新規メッセージを待つ部分ではそのまま待つコードをかける.


なお,yawsでそのまま実行すると,PIPELININGの相性問題が生じます.
yawsのソースの yaws.erl を
yaws.erl: ["Server: Microsoft-IIS/5.0; Yaws/", yaws_generated:version(), " Yet Another Web Server\r\n" |
というように書き換えれば解決.

最初HTTP/1.1を1.0に書き換えたのだけど,IE6でうまく表示できなくなってしまった.
1.0なのに1.1規格onlyな応答を返したりしていたせいかもしれない.
上のIISを騙る方法では無事成功.IE6/IE7/Firefox/Operaでは動作確認できた.

この辺は,Comet の正しい使い方,が大変参考になりました.


----

追記:改良した.

入退室管理,時刻表示,などをつけて,色々まずかった部分を修正.
だいぶソースは長くなったものの,Erlangが250行弱に,HTML+JSが100行くらい.

Comet チャットサンプル

ソース:
chat.erl (ログの管理など)
chat.yaws (HTTP処理)
index.html (HTML+JS)
※別途jqueryが必要

----

追記:更に改良した.

・IE7利用時にクッキーの管理がまずく,うまく入室できない事がある問題を修正
・ログをdetsで保存するようにした.再起動してもログが残るように.
・ログの保存量を100行に.1000行だと,チャット画面を開いたときに重そうだったので.
 JSONで返したのをブラウザで描画するのではなくて,HTMLに変換済みのデータを最初に渡すようにすれば改善できそうだけど,めんどくさそうだったので(^^;

Comet チャットサンプル

ソース:
chat.erl (ログの管理など)
index.yaws (HTTP処理)
index.html (HTML+JS)
※別途jqueryが必要

ソースはerl+yawsで270行くらいになった.


----

追記:
色々修正した.

・デザインをちょっとだけ.参加者一覧が上にでるとじゃまなので・・・
・発言と同時に誰かのタイムアウト処理がされたときなどに,2回連続で再接続させるのは無駄なのでタイミングを調整
・発言が続くとタイムアウトしない問題を修正
・入室直後に参加者一覧に追加されない問題を修正
・チャットの部屋をブックマークして再度参照した場合,クッキーの関係で正常に処理されないことがあったので,リダイレクトさせるのをやめた
・同じ名前で入った場合,重複しない名前をつけるように修正

ソース:
chat.erl (ログの管理など)
index.yaws (HTTP処理)
chat_temp.html (HTML+JS)
※別途jqueryが必要

ソースが300行を超えた.
とはいえ,他の言語でやることを想像したら,かなり短いコードで書けているとは思う.

一応,簡単な負荷試験として,
chatbench.erl
とか書いて,1000人がROMっている状況でテストしてみたところ,まぁ普通に動いていたので,
そのくらいまでは耐えられそう.
Windows上で実験していたら,ポートが不足したのか,しばらくやるとクライアント側がエラーを出していたけれども(^^;

----

JSONP方式に切り替えた.
JSONP+CometはOperaでは無理と思っていたのだけど,下記を参考にさせていただいたところ,ちゃんと動くようになった.感謝.
Operaでも非同期リクエストが並列処理できる img-JSONP

IMGタグでキャッシュさせるというアイディアがすごい・・・

Comet チャットサンプル

ソース:
chat.erl (ログの管理など)
index.yaws (HTTP処理)
chat_temp.html (HTML+JS)
※別途jqueryが必要

今のサンプルは,ドメインが1つだけども,リロードするたびに適当なホスト名を生成して,ワイルドカードドメインを使えば,ドメインあたり2接続の問題は回避可能.
これでなんとか解決かな?
Safariで動作確認取れていないのが気がかりなところだけども・・・

% Mac持ってないので(^^;


追記:設置方法を別エントリに書きました.
ErlangでComet設置方法

| | Comments (3) | TrackBack (1)

Monday, June 04, 2007

Erlangの+Aオプション

Erlangでは,+Aオプション付きで起動することで,async thread pool を作成し,ブロックするいくつかのオペレーションを別スレッドで処理することが出来る.
デフォルトは0なので,通常は無効.

通常は非smpカーネルなら1つ,smpカーネルならCPUコアと同数のスケジューラスレッドが起動し,このスケジューラスレッドがErlangのコードを実行する.
(-smp オプションをつけたときは,+S オプションでスケジューラスレッドの数を変更可能)
で,ブロックするシステムコールを呼ぶと,スケジューラスレッドが応答待ちをしてしまうので,効率が悪くなってしまう.
+A オプションを使って async thread pool を追加すると,このブロックしてしまう処理をasyncスレッドへ移動でき,スケジューラスレッドは別のErlangコードの実行を続けることが出来る.
その結果,I/O待ちが多く発生するようなコードがかなり高速化できる.

※ただし,どのような処理がasyncスレッドを使用するのかは不明...

smpカーネルを使ってスケジューラスレッドを多数起動しても似たような効果がでるものの,async thread poolの方がブロックされる処理に特化されている分,軽い(?)ような感じ.(ベンチは取っていないけれども)

しかし,この+Aオプションを使うと,Linux環境で segmentation fault が起きて Erlang VM が落ちるという問題が発生.
早速MLにバグ報告したところ,週末に報告したのに1.5日でパッチが届いた.素早い.
(というか休日に対応していただいた感じ?)

このオプションを試そうと思う方は,R11B-5 がリリースされるまでは,
[erlang-bugs] +A option causes segmentation fault on Linux
にあるパッチを適用すれば,直ります.


C言語でC10KなサーバをI/O非同期方式(epollとか)で書く場合は,
・syncを別スレッドに隔離して実行して,実行後に応答を返す
・事前に別スレッドで必要なデータをmlockかけておいてから,本体のスレッドで処理する
などなど,結構ややこしい回避策をとる必要があるわけで,その辺のめんどくささを考えると,Erlangの+Aオプションは非常に手軽.

いくつかテストコードを書いてベンチを取った限りでは,C言語よりは5倍以上は遅いようだけども(動的な型言語だし?),動的な型言語の中では一番速いんじゃないかと思える性能.
Cより手軽で,Perlとかより高速,という中間的な部分で今後も活用していけそう.

社内でErlang使える人がほとんどいないのが問題だけども(^^;

| | Comments (0) | TrackBack (0)

Sunday, June 03, 2007

Erlangで数字→ローマ数字変換

勉強会に参加していないので宿題の内容は知らないのだけども,すぐに書けそうだったので...
大きな数を与えると遅そうだけど,たぶんそんな使い方はしないんじゃないかなぁと思って,手抜き.

-module(i2r).
-export([i2r/1]).

i2r(N) ->
  i2r(N, "").

i2r(N, S) when N >= 1000 ->
  i2r(N - 1000, S ++ "M");
i2r(N, S) when N >= 500 ->
  i2r(N - 500, S ++ "D");
i2r(N, S) when N >= 100 ->
  i2r(N - 100, S ++ "C");
i2r(N, S) when N >= 50 ->
  i2r(N - 50, S ++ "L");
i2r(N, S) when N >= 10 ->
  i2r(N - 10, S ++ "X");
i2r(N, S) when N >= 5 ->
  i2r(N - 5, S ++ "V");
i2r(N, S) when N >= 1 ->
  i2r(N - 1, S ++ "I");
i2r(_N, S) ->
  S.

| | Comments (0) | TrackBack (0)

« May 2007 | Main | July 2007 »