Hatena::ブログ(Diary)

とりあえず、一応、その方向で

2009-12-01

erl_nifがお手軽すぎる件

| 00:11

(注: R13B04からインタフェースが変わったので、以下の内容はR13B03のみ有効です。)

R13B03からNative Implemented Functions(NIFs)が導入されました。マニュアルにある通り、外部のC関数を呼び出すのがこれまでのport driverを使う方法より非常に簡単になってます。NIFの実装のソースはまだあんまり見ていないのですが、このへんを見ると、BIFの呼び出しと同じオーバヘッドで呼び出せるのではないかと思われます。

試しにTokyo Cabinetを呼んでみた

というわけで、ためしにTokyo CabinetのErlangバインディングを(乱暴に)書いてみました。ポインタをunsigned longにキャストするとか固定長の配列に長さのわからない文字列を書き込んでるとか突っ込みどころは満載ですが、ご容赦ください。なお、ここでは、必要最小限のAPIという意味でtcadb{new,del,open,close,put2,get2}のみを実装してます。binaryの扱いがわかれば、tcadb{put,get}の実装も容易かと。

Erlang

Erlang側はこんな感じです。nifのドライバを読み込むための関数と、nifが読み込まれていないときのプレースホルダのみになってます。

-module(tc).

-export([init/0,
        adbnew/0,
        adbdel/1,
        adbopen/2,
        adbclose/1,
        adbput2/3,
        adbget2/2
        ]).
        
init() ->
  erlang:load_nif("./tc", 0).

adbnew() ->
  {error, nif_library_not_loaded}.

adbdel(_Adb) ->
  {error, nif_library_not_loaded}.

adbopen(_Adb, _Name) ->
  {error, nif_library_not_loaded}.

adbclose(_Adb) ->
  {error, nif_library_not_loaded}.

adbput2(_Adb, _KeyStr, _ValueStr) ->
  {error, nif_library_not_loaded}.

adbget2(_Adb, _KeyStr) ->
  {error, nif_library_not_loaded}.
C側

C側は、マニュアルの作法に従ってこんな感じです。繰り返しますが、とっても乱暴なコーディングです。

#include <tcutil.h>
#include <tcadb.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include "erl_nif.h"

static int my_enif_get_string(ErlNifEnv *env, ERL_NIF_TERM list, char* buf)
{
  ERL_NIF_TERM cell, head, tail;
  int val;
  
  while (enif_get_list_cell(env, list, &head, &tail)) {
    if (!enif_get_int(env, head, &val)) {
      return 0;
    }
    *buf = (char)val;
    buf++;
    list = tail; 
  }
  *buf = '\0';
  
  return 1;
}

static ERL_NIF_TERM nif_adbnew(ErlNifEnv* env)
{
  unsigned long adb;
  
  adb = (unsigned long)tcadbnew();
  
  return enif_make_ulong(env, adb);
}

static ERL_NIF_TERM nif_adbdel(ErlNifEnv* env,
                               ERL_NIF_TERM adb_tm)
{
  unsigned long adb;
  
  enif_get_ulong(env, adb_tm, &adb);
  tcadbdel((TCADB*)adb);  
  return enif_make_atom(env, "ok");
}

static ERL_NIF_TERM nif_adbopen(ErlNifEnv* env,
                                ERL_NIF_TERM adb_tm,
                                ERL_NIF_TERM filename_tm)
{
  char filename[1024];
  unsigned long adb;
  
  enif_get_ulong(env, adb_tm, &adb);
  my_enif_get_string(env, filename_tm, filename);
  if (tcadbopen((TCADB*)adb,filename)) {
    return enif_make_atom(env, "ok");
  } else {
    return enif_make_atom(env, "error");
  }
}

static ERL_NIF_TERM nif_adbclose(ErlNifEnv* env,
                                 ERL_NIF_TERM adb_tm)
{
  unsigned long adb;
  
  enif_get_ulong(env, adb_tm, &adb);
  if (tcadbclose((TCADB*)adb)) {
    return enif_make_atom(env, "ok");
  } else {
    return enif_make_atom(env, "error");
  }
}

static ERL_NIF_TERM nif_adbput2(ErlNifEnv* env,
                                ERL_NIF_TERM adb_tm,
                                ERL_NIF_TERM kstr_tm,
                                ERL_NIF_TERM vstr_tm)
{
  unsigned long adb;
  char kstr[1024];
  char vstr[1024];
  
  enif_get_ulong(env, adb_tm, &adb);
  my_enif_get_string(env, kstr_tm, kstr);
  my_enif_get_string(env, vstr_tm, vstr);
  if (tcadbput2((TCADB*)adb, kstr, vstr)) {
    return enif_make_atom(env, "ok");
  } else {
    return enif_make_atom(env, "error");
  }
}

static ERL_NIF_TERM nif_adbget2(ErlNifEnv* env,
                                ERL_NIF_TERM adb_tm,
                                ERL_NIF_TERM kstr_tm)
{
  unsigned long adb;
  char kstr[1024];
  char *vstr;
  ERL_NIF_TERM vstr_tm;

  enif_get_ulong(env, adb_tm, &adb);
  my_enif_get_string(env, kstr_tm, kstr);
 
  if ((vstr = tcadbget2((TCADB*)adb, kstr))) {
    vstr_tm = enif_make_string(env, vstr);
    free(vstr);
    return enif_make_tuple(env, 2, enif_make_atom(env, "value"), vstr_tm);
  } else {
    return enif_make_atom(env, "error");
  }
}

static ErlNifFunc nif_funcs[] =
{
  {"adbnew", 0, nif_adbnew},
  {"adbdel", 1, nif_adbdel},
  {"adbopen", 2, nif_adbopen},
  {"adbclose", 1, nif_adbclose},
  {"adbput2", 3, nif_adbput2},
  {"adbget2", 2, nif_adbget2},
};

ERL_NIF_INIT(tc,nif_funcs,NULL,NULL,NULL,NULL)
実行
$ gcc -undefined suppress -flat_namespace -fPIC -shared -o tc.so tc.c -I /usr/local/lib/erlang/usr/include -ltokyocabinet
$ erl
Erlang R13B03 (erts-5.7.4) [source] [smp:2:2] [rq:2] [async-threads:0] [kernel-poll:false]

Eshell V5.7.4  (abort with ^G)
1> tc:init().
ok
2> ADB=tc:adbnew().
3175920
3> tc:adbopen(ADB, "casket.tch").
ok
4> tc:adbput2(ADB, "foo", "hop").
ok
5> tc:adbput2(ADB, "bar", "step").
ok
6> tc:adbput2(ADB, "baz", "jump").
ok
7> tc:adbget2(ADB, "foo").        
{value,"hop"}
8> tc:adbclose(ADB).      
ok
9> tc:adbdel(ADB).
ok
10> 

なんとか動いているようです。tchmgrで見てもちゃんと書き込まれています。

$ tchmgr list -pv casket.tch 
foo	hop
bar	step
baz	jump

R13B03では"The NIF concept is introduced in R13B03 as an EXPERIMENTAL feature. The interfaces may be changed in any way in coming releases. The API introduced in this release is very sparse and contains only the most basic functions to read and write Erlang terms."と警告されているので、いろいろなライブラリをNIFで書くのはまだ避けたほうがよいかと思いますが、用途がマッチしていると思った開発者の方は、NIFで書き直してみてErlang処理系開発者にどんどんフィードバックしていくのも必要ですね。

jj1bdxjj1bdx 2009/12/09 21:11 erlang-questions mailing listで紹介されてました。
http://groups.google.com/group/erlang-programming/browse_thread/thread/66fd0458f47ff8a9

トラックバック - http://d.hatena.ne.jp/vostok92/20091201/1259680319