Hatena::Groupnodejs

Node.jsで遊ぶよ

 | 

2010-12-14

C++ で node.js ライブラリを作る・その2

09:44

前回の続き。

コールバックベースの非同期関数を作る。

具体的には、前回こんな感じだった write を EIO スレッドのほうでやることにする。

    int Write (const char *str) {
      return fprintf(fp, "%s\n", str);
    }

    static Handle<Value>
    Write (const Arguments& args) {
      HandleScope scope;
      Unwrap<File>(args.This())->Write(*String::Utf8Value(args[0]));
      return Undefined();
    }

こうなる。C++ なのに C 言語風なのはご愛敬。struct じゃなくて class にしてもよかったんだけど、行数が増えるので今回は割愛。

    int Write (const char *str) {
      return fprintf(fp, "%s\n", str);
    }

    struct WriteData {
      File *file;
      char *str;
      Persistent<Value> cb;
    };

    static int
    AfterWrite(eio_req *req) {
      HandleScope scope;
      ev_unref(EV_DEFAULT_UC);

      WriteData *write_data = (WriteData*)(req->data);

      if (write_data->cb->IsFunction()) {
        Local<Value> argv[1];
        argv[0] = req->result < 0 ?
          Local<Value>::New(String::New("Error")) :
          Local<Value>::New(Undefined());
        Persistent<Function>::Cast(write_data->cb)->Call(
            write_data->file->handle_, 1, argv);
      }

      write_data->file->Unref();
      write_data->cb.Dispose();
      free(write_data->str);
      free(write_data);
      return 0;
    }

    static int
    EIO_Write(eio_req *req) {
      WriteData *write_data = (WriteData*)(req->data);
      req->result = write_data->file->Write(write_data->str);
      return 0;
    }

    static Handle<Value>
    Write (const Arguments& args) {
      HandleScope scope;

      WriteData *write_data = (WriteData *)malloc(sizeof(WriteData));
      write_data->file = Unwrap<File>(args.This());
      write_data->str = strdup(*String::Utf8Value(args[0]));
      write_data->cb = Persistent<Value>::New(args[1]);

      eio_custom(EIO_Write, EIO_PRI_DEFAULT, AfterWrite, write_data);

      write_data->file->Ref();
      ev_ref(EV_DEFAULT_UC);
      return Undefined();
    }

何やってるのか一発で分かる人は少ないと思うので説明。下のほうから。

      eio_custom(EIO_Write, EIO_PRI_DEFAULT, AfterWrite, write_data);

これが任意の操作を EIO スレッドのほうで実行するための命令。EIO スレッドの話は↓に書いた。

EIO_Write は EIO スレッドのほうで実行される関数AfterWriteEIO_Write が終わった後でメインスレッドで実行される関数。どちらも eio_req *req というポインターが渡される。eio_req には data という任意のポインターを入れられるフィールドが開けてあるので、そこに write_data を渡してやる。

write_data はこんなふうに作った。

      WriteData *write_data = (WriteData *)malloc(sizeof(WriteData));
      write_data->file = Unwrap<File>(args.This());
      write_data->str = strdup(*String::Utf8Value(args[0]));
      write_data->cb = Persistent<Value>::New(args[1]);
  • file は、今書いている File オブジェクト
  • str はファイルに書き出す文字。
    • String::Utf8ValueHandleScope の消滅とともに GC されてしまうので、strdup で新しく malloc してある。
  • cb はコールバック。
    • Persistent Handle を作っているので、非同期操作を実行されている間どこからも参照されていない状態になるけど GC されない。
    • Persistent と Local の違いはここに書いた。

そして、write 関数自体は undefined を返す。

      write_data->file->Ref();
      ev_ref(EV_DEFAULT_UC);
      return Undefined();
  • node::ObjectWrap::Ref をやることによって、JSオブジェクトGC されなくする。
  • ev_ref(EV_DEFAULT_UC); は EIO スレッドの参照カウントを増やして node.js のイベントループを抜けてしまわないようにする。
    • 参照されているスレッドの数がゼロになると node.js が終了する、と node.cc のコメントに書いてあった。

次に EIO スレッドで実行される部分を見る。

    int Write (const char *str) {
      return fprintf(fp, "%s\n", str);
    }

    static int
    EIO_Write(eio_req *req) {
      WriteData *write_data = (WriteData*)(req->data);
      req->result = write_data->file->Write(write_data->str);
      return 0;
    }

やってることは見たら分かると思う。

V8スレッドセーフではないので(スレッドセーフにするには各スレッドでグローバルコンテクスト初期化しないといけないので)、ここでは V8オブジェクトを触ってはいけない。

req->result の型は ssize_t で、普通はエラーコードなどを入れる。fprintf の返り値は、「成功すれば書きこまれたバイト数、失敗すればマイナス」となるらしい。(すっかり失念してた…)

最後にメインスレッドに戻って、AfterWrite を実行。

    static int
    AfterWrite(eio_req *req) {
      HandleScope scope;
      ev_unref(EV_DEFAULT_UC);

      WriteData *write_data = (WriteData*)(req->data);

      if (write_data->cb->IsFunction()) {
        Local<Value> argv[1];

        argv[0] = req->result < 0 ?
          Local<Value>::New(String::New("Error")) :
          Local<Value>::New(Undefined());

        Persistent<Function>::Cast(write_data->cb)->Call(
            write_data->file->handle_, 1, argv);
      }

      write_data->file->Unref();
      write_data->cb.Dispose();
      free(write_data->str);
      free(write_data);
      return 0;
    }
  • ev_unref(EV_DEFAULT_UC); で EIO スレッドの参照カウントを減らす。
  • コールバックが渡されていればそれを実行。
    • v8::Function::Call には JS の this にあたるオブジェクトを与えるのだが、それは node::ObjectWrap::handle_ で元々の this だったものが取れるので、それを渡してる。
    • node.js の決まりで、コールバックに渡す最初の引数はエラーということになっている。(エラーがなければ第一引数を undefined にしたりもありうると)
  • 最後に色んな物を GC する。
    • write_data->file->Unref()JSオブジェクトGC してもいいよという意味。
    • write_data->cb->Dispose() は Persistent Handler を GC に回してもいいよという意味。

さて、気になるベンチマークなんだけど、非同期にするとかなり遅くなる。

同期。(test.js)

var File = require('./build/default/file').File;
var file = new File('bar.txt');

var N = 10000;
for (var i = 0; i < N; i++) {
  file.writeSync(i);
}

console.log('done');

非同期。(async.js)

var File = require('./build/default/file').File;
var file = new File('foo.txt');

var N = 10000;
var n = 0;

for (var i = 0; i < N; i++) (function test(i) {
  file.write(i, function(err) {
    if (err) {
      console.log([i, err]);
    }
    if (++n === N) {
      console.log('done');
    }
  });
}(i));

結果。

% time node test.js
done
node test.js  0.02s user 0.06s system 89% cpu 0.094 total
% time node async.js
done
node async.js  0.07s user 2.62s system 99% cpu 2.710 total

30倍近く…

これは、EIO スレッドとの通信にかかるオーバーヘッドが非常に大きいことに加え、非同期にしてるのに結局一つのファイルを扱っていることなどが考えられる。色んなジョブを並列で扱うともっと変わってくるのかもしれない。


気が向いたら「その3」も書く。

その3は EventEmitter の話にしようかと思ったけど既にあった→node.jsのモジュール作り方メモ - 三次元日誌

zbuvfloiklzbuvfloikl2013/12/17 20:42vmnytopefkt, <a href="http://www.qnfuspsblg.com/">zjqggvjhso</a> , [url=http://www.nmcivaxpkg.com/]bzenphneyq[/url], http://www.pppqlojipk.com/ zjqggvjhso

tftebsbvoatftebsbvoa2014/12/03 10:33mdqqaopefkt, <a href="http://www.rylimascxd.com/">emsazfybwd</a> , [url=http://www.hzcicxywnx.com/]fajilazsrb[/url], http://www.siuuihrnpd.com/ emsazfybwd

SailiphieseSailiphiese2017/05/04 02:22http://undeclaiming.xyz <a href="http://undeclaiming.xyz">norsk kasino</a> http://undeclaiming.xyz - norsk kasino

 |