Hatena::ブログ(Diary)

cooldaemonの備忘録 RSSフィード

2009-05-05

libxml2 の SAX インターフェースを試してみた

Source はこちら

iPhone アプリの開発中に XML をパースしたくなったのだけれど、サンプルコードの XML Performance をコンパイルして実行してみると、libxml2 の SAX インターフェースを使った方が、NSXMLParser を使うより三倍以上早かったので、libxml2 を使う事にした。

NSXMLParser も内部で libxml2 を利用しているが、libxml2 を直接使う場合、xmlCreatePushParserCtxt() で作ったパーサーコンテキストに対して xmlParseChunk() を使い、ダウンロード中の XML を少しずつ追加しながらパースを行うという芸当が可能な為、速度が早い。

ただし、XML ではなく HTML をパースする場合、かつ、HTML_PARSE_RECOVER などのオプションを指定したい場合、一度 DOM ツリーを作成する必要がある為、残念ながら SAX インターフェースは使えない(本当かな?識者の突っ込み希望)ので、NSXMLParser を使った方が良いのかな?

NSXMLParser は SAX 専用である為、オプションを指定する場合は、DOM 専用の NSXMLDocument を使う必要がある。オプションの種類は この辺を参照の事。

ちなみに、SAX のコールバック関数を登録する際に下記のようにした。

SAXHander.initialized = XML_SAX2_MAGIC;

これを登録しておかないと、SAX2 用のコールバック関数を呼んでくれない。(これで少しハマった orz)

2008-02-14

perl erlang プロセス間で直接メッセージを送受信する

単純に erl_interface を Inline::C から使っただけなんですが(w;

erlang 側の準備

特別な準備は無し。

とりあえず、送受信するプロセスを適当に作っておく。

-module(pingpong).
-compile(export_all).

start() -> register(pingpong, spawn(?MODULE, loop, [])).
stop()  -> pingpong ! stop.

loop() ->
  receive
    stop ->
      io:fwrite("~w:stop.~n", [self()]),
      exit(ok);
    {message, Pid, Message} ->
      io:fwrite("Pid:~w~nMessage:~s~n", [Pid, Message]),
      Pid ! {message, self(), "pong"};
    Other ->
      io:fwrite("Other:~w~n", [Other])
  end,
  loop().
% erlc pingpong.erl
% erl -name test@`hostname` -s pingpong start

perl 側の準備

http://labs.miu.vc/svn/cooldaemon/perl/Erlang-Interface/lib を任意の場所に設置。

lib/Erlang/Interface/InlineC.pm の下記の LIBS と INC を適切なパスに書き換える。

use Inline C => Config    ## no critic
  => CCFLAGS => '-std=c99'
  => LIBS => '-L/opt/local/lib/erlang/lib/erl_interface-3.5.5.3/lib -lerl_interface -lei'
  => INC  => '-I/opt/local/lib/erlang/lib/erl_interface-3.5.5.3/include';

erlang プロセスとメッセージを送受信するスクリプトを書く。

#!/usr/bin/env perl

use strict;
use warnings;
use feature qw(:5.10);

use version; our $VERSION = qv('0.0.1');

use Carp;
use English qw(-no_match_vars);

use Erlang::Interface;

my $eif = Erlang::Interface->new;
say $eif->node();
my $fd = $eif->connect(alive => 'test');

my $atom = Erlang::Interface::Eterm->make_atom('message');
my $string = Erlang::Interface::Eterm->make_string('ping');
my $pid = $fd->self_pid;

my $tuple = Erlang::Interface::Eterm->make_tuple($atom, $pid, $string,);
$fd->reg_send('pingpong', $tuple) or croak 'send error.';

my $recv = $fd->receive() or croak 'receive error.';
if ($recv->is_tuple) {
    for ($recv->value) {
        if ($_->is_pid) {
            $fd->send($_, $tuple) or croak 'send error';
            next;
        }
        say $_->value;
    }
}

試す

% perl pingpong.pl
A9D28758-DAC7-11DC-89C1-DF2240E9B49D@xxxxxx.jp
message
pong

今後

rpc を実装する。

対応した

my $string = Erlang::Interface::Eterm->make_string('hello!!');
my $args   = Erlang::Interface::Eterm->make_list($string);

my $recv0 = $fd->rpc('io', 'fwrite', $args) or croak 'rpc error.';
say $recv->value;

$fd->rpc_send('io', 'fwrite', $args) or croak 'rpc_send error.';
my $recv1 = $fd->rpc_receive() or croak 'rpc_receive error.';
say $recv1->value;

Global Names に対応する。

2008-02-12

erl_interface メモ

C と Erlang ノード上のプロセスの間で、直接メッセージを送受信できる便利なライブラリ erl_interface を試したみた。

以下、メモと言うより code snippets。

pingpong.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "erl_interface.h"
#include "ei.h"

extern const char *erl_thisnodename(void);
extern short erl_thiscreation(void);
#define SELF(fd) erl_mk_pid(erl_thisnodename(), fd, 0, erl_thiscreation())

void init();
int  connect_and_get_sockfd();
void send_ping(int);
void recv_pong(int);
int  validate_message(ETERM*);

int main() {
    init();

    int sockfd = connect_and_get_sockfd();
    printf("Connect OK\n");

    send_ping(sockfd);
    recv_pong(sockfd);

    erl_close_connection(sockfd);
    return 0;
}

void init() {
    erl_init(NULL, 0);

    int  creation = 1;
    char *cookie = "foobar";

    struct in_addr addr;
    addr.s_addr = inet_addr("0.0.0.0");
    char *host  = "localhost";
    char *alive = "ctest";
    char node[strlen(alive) + strlen(host) + 1];
    sprintf(node, "%s@%s", alive, host);
    if (!erl_connect_xinit(host, alive, node, &addr, cookie, creation))
        erl_err_quit("erl_connect_xinit failed");
}

int connect_and_get_sockfd() {
    int  sockfd;
    char *alive = "test";
    struct in_addr addr;

    addr.s_addr = inet_addr("0.0.0.0");
    if ((sockfd = erl_xconnect(&addr, alive)) < 0)
        erl_err_quit("erl_connect failed");

    return sockfd;
}

void send_ping(int sockfd) {
    ETERM *arr[3], *emsg;

    arr[0] = erl_mk_atom("message");
    arr[1] = SELF(sockfd);
    arr[2] = erl_format("~s", "ping");
    emsg   = erl_mk_tuple(arr, 3);

    erl_reg_send(sockfd, "pingpong", emsg);

    for (int i=0; i < 3; i++)
        erl_free_term(arr[i]);
    erl_free_term(emsg);
}

void recv_pong(int sockfd) {
    int rc;
    int size = 1024;
    unsigned char *buf = malloc(size);
    ErlMessage emsg;

    if (
      (rc = erl_xreceive_msg(sockfd, &buf, &size, &emsg)) == ERL_MSG
    ) {
        if (!validate_message(emsg.msg))
            erl_err_quit("validate error.");

        printf(
            "Message: {%s, %d, %s}\n",
            ERL_ATOM_PTR(erl_element(1, emsg.msg)),
            ERL_PID_NUMBER(erl_element(2, emsg.msg)),
            erl_iolist_to_string(erl_element(3, emsg.msg))
        );

        erl_free_term(emsg.msg);
        erl_free_term(emsg.to);
    }

    free(buf);
}

int validate_message(ETERM *message) {
    if (!ERL_IS_TUPLE(message)) {
        puts("Message is't TUPLE.");
        return 0;
    }

    if (erl_size(message) != 3) {
        puts("TUPLE size is't 3.");
        return 0;
    }

    if (!ERL_IS_ATOM(erl_element(1, message))) {
        puts("Elem of 1st in TUPLE is't ATOM.");
        return 0;
    }

    if (!ERL_IS_PID(erl_element(2, message))) {
        puts("Elem of 2nd in TUPLE is't PID.");
        return 0;
    }

    if (!ERL_IS_LIST(erl_element(3, message))) {
        puts("Elem of 3rd in TUPLE is't LIST.");
        return 0;
    }

    ETERM *head, *list = erl_element(3, message);
    while (!ERL_IS_EMPTY_LIST(list)) {
        head = ERL_CONS_HEAD(list);
        list = ERL_CONS_TAIL(list);
        if (ERL_IS_INTEGER(head)) continue;
        puts("Elem of 3rd in TUPLE is't STRING.");
        return 0;
    }

    return 1;
}

Makefile

CC=gcc
ERL=/path/to/erlang/lib/erl_interface-X.X.X.X

ALL : pingpong

pingpong : pingpong.c
        $(CC) -std=c99 -o hello hello.c \
                -I$(ERL)/include \
                -L$(ERL)/lib -lerl_interface -lei

pingpong.erl

-module(pingpong).
-compile(export_all).

start() -> register(pingpong, spawn(?MODULE, loop, [])).
stop()  -> pingpong ! stop.

loop() ->
  receive
    stop ->
      io:fwrite("~w:stop.~n", [self()]),
      exit(ok);
    {message, Pid, Message} ->
      io:fwrite("Pid:~w~nMessage:~s~n", [Pid, Message]),
      Pid ! {message, self(), "pong"};
    Other ->
      io:fwrite("Other:~w~n", [Other])
  end,
  loop().
% erl -name test@localhost -s pingpong start
% make
% ./pingpong
Connect OK
Message: {message, 45, pong}

Inline::C を使って perl から使える事を確認した。後でそっちの module もさらす。

さらした -> id:cooldaemon:20080214

参考URI