並列関数型言語Erlangのねた。
つくったもの
Link
せっかくだからErlang版も貼っとく。
じつはずっと昔に書いたコードだったりする。
-module(udpmsg).
-export([send/2]).
-include_lib("kernel/include/inet.hrl").
-define(IPMSG_VERSION, 16#0001).
-define(IPMSG_DEFAULT_PORT, 16#0979).
-define(IPMSG_SENDMSG, 16#020).
-define(DEFAULT_UDP_OPTS, [
{active, false},
% {broadcast, true},
{reuseaddr, true}
]).
send_format(Num, Host, User, Cmd) ->
lists:flatten(io_lib:format("~.10b:~.10b:~s:~s:~.10b:", [?IPMSG_VERSION, Num, User, Host, Cmd])).
send_format(Num, Host, User, Cmd, Extra) ->
lists:flatten(io_lib:format("~.10b:~.10b:~s:~s:~.10b:~s", [?IPMSG_VERSION, Num, User, Host, Cmd, Extra])).
send(Host, Msg) ->
Num = erlang:phash(erlang:localtime(), 16777215),
case gen_udp:open(0, ?DEFAULT_UDP_OPTS) of
{ok, Socket} ->
EntMsg = send_format(Num, "myhost", "myname", ?IPMSG_SENDMSG, Msg),
gen_udp:send(Socket, Host, ?IPMSG_DEFAULT_PORT, EntMsg),
gen_udp:close(Socket);
Error ->
Error
end.
ErlangにはHiPE(High Performance Erlang)というしかけがあって、
beam(erlangのためのvm)からネイティブコードを呼びだすよう変換することができる(hipeに関するスライド)。
FreeBSDでもx86だとhipeを有効にしてビルドする(FreeBSD-cvs:ports/lang/erlang/)ので、
OpenBSDでも動かないとおかしいわな、というわけで、otp-R11B-1をいじってみた。
まあ結果としては動いたのだが、別の問題が発覚したのでメモ。
さて、Erlangでhipeを有効にするには./configureに--enable-hipeを追加するだけでよろしい。OpenBSD-cvs:ports/lang/erlang/Makefileを見て、
./configure --disable-jinterface --disable-odbc --enable-threads --enable-kernel-poll --enable-hipe
このような感じか。
で、そのままだと、erts/emulator/hipe/hipe_x86_signal.cでこける。
ざっとコードを見たところ、OpenBSD-man:sigaltstackをdlsymして自前のsigaltstackでラップしているらしいことがわかる。
以前のエントリ(OpenBSD/2006/07/27/dlopen)を踏まえて、
このようなパッチを書いてみた: files:openbsd/otp_src_R11B-1-patch-erts_emulator_hipe_hipe_x86_signal.c
で、上のパッチを書いているときに気が付いたのだが、hipe_x86_signal.cから切り出した部分からのテストコード、
$ cat sigstack.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
/*
* Set alternate signal stack for the invoking thread.
*/
static void set_sigaltstack(void *ss_sp)
{
struct sigaltstack ss;
ss.ss_sp = ss_sp;
ss.ss_flags = SS_ONSTACK;
ss.ss_size = SIGSTKSZ;
if (sigaltstack(&ss, NULL) < 0) {
/* might be a broken pre-2.4 Linux kernel, try harder */
ss.ss_flags = 0;
if (sigaltstack(&ss, NULL) < 0) {
perror("sigaltstack");
abort();
}
}
}
main()
{
static unsigned long my_sigstack[SIGSTKSZ/sizeof(long)];
set_sigaltstack(my_sigstack);
}
が、-pthread付きでビルドしたときと、無しのときとで挙動が異なる。
$ cc sigstack.c
$ ./a.out
$
$ cc sigstack.c -pthread
$ ./a.out
sigaltstack: Invalid argument
$
結局のところ、
OpenBSD-cvs:src/lib/libpthread/uthread/uthread_sigaltstack.c
33行目はどうみても、
if (ss == NULL) {
の間違いです。本当にありがとうございました。
本当にコードレビューしているのか怪しいものである。
ということでlibpthreadを再構築して、Erlangをビルド。
$ erl
Erlang (BEAM) emulator version 5.5.1 [source] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.5.1 (abort with ^G)
1>
hipeが有効になった。
ちなみにhipe付きのbeamコードは、
$ erlc +native file
とするか、erl上で、
> c(module, [native, {hipe, ['O3']}]).
とすればよい。めでたしめでたし。
Erlangと副作用
関数型言語であるErlangは副作用を起こす操作は一般の文法上には存在していない(代入もcall/ccも無い)が、
それでもそのような結果を引き起こすことは可能だ。
Erlangで副作用を発生させる手段には2種類ある(他にもあったらやんわりと指摘頂きたい)。
ドライバは外部のオブジェクト(CやJavaやその他実行ファイル)とリストをやりとりするものだから、
当然副作用は起こりうる(というより、この場合は副作用を起こすことが目的である)ので、ここでは説明しない。
workerとして振る舞わせるには、例えば、
-behaviour(gen_server).
とプログラム中に書くことでgen_serverとして機能するようになる。
gen_serverとして書かれたコードは5つのコールバック関数を持ち、
supervisorから呼び出されるようになる。
Erlangでは、
Pid = spawn(?MODULE, foo, []),
Pid ! Message,
...
のようなコードを用いて、サーバ側の状態の変更をさせるのが一般的なのだが、
gen_serverでは、そうする必要が無くなる(単にコールバックをトリガしてやるだけでよい)。
その代償として、
その「状態」を管理することができなくなる(状態の変更はgen_serverとsupervisorの間で行なわれる)。
これが副作用に見えるわけだ。
例を挙げる。
-module(inc).
-behaviour(gen_server).
-export([start_link/0]).
-export([inc/0]).
-export([init/1, handle_call/3]).
start_link() ->
gen_server:start_link({local, inc}, inc, [], []).
inc() ->
gen_server:call(?MODULE, inc).
init(_Args) ->
process_flag(trap_exit, true),
{ok, 0}.
handle_call(inc, _From, State) ->
Reply = State,
{reply, Reply, State + 1}.
先ほど5つのコールバック関数と書いたが、
ここではinit/1とhandle_call/3のみ使用した。
start_link/0とinc/0が利用者側のコードで、
他の関数は触ることができないようになっている。
状態の変更はhandle_call/3で行なっている。
実行結果は以下のようになる。
1> inc:start_link().
{ok,<x.x.x>}
2> inc:inc().
0
3> inc:inc().
1
4> inc:inc().
2
Erlangに関する全てのエントリ