Fork me on GitHub

2009/02/24


このエントリーをはてなブックマークに追加
memcachedサーバへのアクセスモジュールは数ありますが、一般的にはlibmemcachedが使われる事が多いと思います。
Perlにおいても
  • Cache::Memcached
  • Cache::Memcached::Fast
  • Cache::Memcached::libmemcached
  • Memcached::libmemcached
と数種類存在し、一般的に使用されるlibmemcachedのラッパインタフェースであるCache::Memcached::libmemcachedが使われる事が多い様に思います。
libmemcachedに関しては、以前Windowsへのポーティングを行い、オフィシャルへのパッチ送付も行いました。
成果物としてはcodereposに置いてあります。オフィシャルからもリンクを張って頂けるようになりました。
さらにCache::Memcached::Fastについても、以前Windowsへのポーティングを行い、Cache::Memcached::Fast version 0.13に取り込まれました。
つまり特殊な事をせずにWindowsから利用出来る高速なPerlのmemcachedクライアントライブラリとしてはCache::Memcached::Fastになります。
ちなみこのCache::Memcached::Fast、実はlibmemcachedと比較しても格段に速く、tokuhiromさんが取ったベンチマークでも素晴らしい結果を叩き出してくれています。

さて今日は、このCache::Memcached::Fastが内部で使用しているXSクライアントモジュールを使用して、高速にmemcachedにアクセスする物を作ってみたいと思います。
このCache::Memcached::FastのXSコードは、Perlに依存した部分とPerlに依存していない部分で分けられており、その後者は一般的なC言語のソースから呼び出しが可能になっています。
なぜこのCache::Memcached::Fastが速いかと言うと、libmemcachedの様に逐次送信を行っているのではなくwritev(2)を使った一括送信を行っているからです。またCache::Memcached::Fastはselect(2)ではなくpoll(2)を使っている為、FD_SETの設定を毎回行わなくて良いというのも微量ではありますが影響しているのではないかと思っています。
このクライアントモジュールは、libmemcachedの様に単一取得(memcached_get)や複数取得(memcached_mget)のAPI呼び出し時に逐次送受信されるのではなく、client_prepare_get/client_prepare_setを使った前準備方式を使っています。これにより複数の問い合わせに対しても内部では一括送信してくれ、一括で受信してくれます。libmemcachedは複数の問い合わせに対してそれぞれ結果待ちをし、全ての問い合わせが完了した時点で制御が戻ります。これについてはMEMCACHED_BEHAVIOR_NO_BLOCKを使用する事でよく似た動作をする事が出来ます。
このクライアントモジュールを使用した実際のサンプルコードは以下の様になります。
/* fast memcached client using client module of Cache::Memcached::Client.
 *   compile   : gcc -o a.exe foo.c libclient.a
 *   for win32 : gcc -o a.exe foo.c libclient.a -lws2_32
 */

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "client.h"

static void* alloc_value(value_size_type value_size, void **opaque) {
    *opaque = (char *) malloc(value_size + 1);
    memset(*opaque, 0, value_size + 1);
    return (void *) *opaque;
}

static void free_value(void *opaque) {
    free(opaque);
}

static void result_store(void *arg, void *opaque, int key_index, void *meta) {
    char* res = (char*) opaque;
    if (res) *(char**)arg = strdup(res);
    else     *(char**)arg = strdup("");
}

int main(int argc, char* argv[]) {
    struct client* c = NULL;
    const char* host = "127.0.0.1";
    const char* port = "11211";
    const char* key = "foo";
    const char* value = "bar";
    struct result_object object;
    char* result = NULL;
    int ret;

    /* initialize client module of Cache::Memcached::Fast */
    c = client_init();

    /* add memcached server */
    client_add_server(c, host, strlen(host), port, strlen(port), 1.0, 1);

    /*--- set function --------------------------------*/
    /* initialize staff context for set */
    object.alloc = NULL;
    object.store = result_store;
    object.free = NULL;
    object.arg = NULL;
    client_reset(c, &object, 1);

    /* prepare for set function */
    printf("setting value of '%s' as '%s'\n", key, value);
    client_prepare_set(c, CMD_SET, 0, key, strlen(key), 0, 0, value, strlen(value));

    /* execute set function */
    client_execute(c);
    /*-------------------------------------------------*/

    /*--- get function --------------------------------*/
    /* initialize staff context for get */
    object.alloc = alloc_value;
    object.store = result_store;
    object.free = free_value;
    object.arg = &result;
    client_reset(c, &object, 0);

    /* prepare for get function */
    printf("getting value of '%s'\n", key);
    client_prepare_get(c, CMD_GET, 0, key, strlen(key));

    /* execute set function */
    client_execute(c);
    /*-------------------------------------------------*/

    printf("result value of '%s' is '%s'\n", key, result);

    free(result);

    return 0;
}
少し変わったコードになりますが、メモリの確保から開放まで自分でハンドリングでき、自前の構造を使った処理も行えるかと思います。
なお、libmemcachedとCache::Memcached::Fastのクライアントモジュールでget/setを繰り返すベンチマークを取ってみました。
まずはlibmemcachedのコード
#include <winsock2.h>
#include <memcached.h>
#include <stdio.h>

#define SERVER_NAME "127.0.0.1"
#define SERVER_PORT 11211
#define KEY "foo"
#define VALUE "bar"

int main(void) {
    memcached_return rc;
    memcached_st *memc;
    char* value;
    int value_length = 0;
    int flags = 0;
    int n;

    memc = memcached_create(NULL);
    memcached_server_add(memc, SERVER_NAME, SERVER_PORT);
    for (n = 0; n < 30000; n++) {
        memcached_set(memc, KEY, strlen(KEY), VALUE, strlen(VALUE), 0, 0);
        value = memcached_get(memc, KEY, strlen(KEY), &value_length, &flags, &rc);
    }
    memcached_free(memc);
    return 0;
}
次にCache::Memcached::Fast(CMF)のクライアントモジュールのコード
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "client.h"

#define SERVER_NAME "127.0.0.1"
#define SERVER_PORT "11211"
#define KEY "foo"
#define VALUE "bar"

static void* alloc_value(value_size_type value_size, void **opaque) {
    *opaque = (char *) malloc(value_size + 1);
    memset(*opaque, 0, value_size + 1);
    return (void *) *opaque;
}

static void free_value(void *opaque) {
    free(opaque);
}

static void result_store(void *arg, void *opaque, int key_index, void *meta) {
    char* res = (char*) opaque;
    if (res) *(char**)arg = strdup(res);
    else     *(char**)arg = strdup("");
}

int main(int argc, char* argv[]) {
    struct client* c = NULL;
    struct result_object object_set, object_get;
    char* result = NULL;
    int n;

    c = client_init();
    client_add_server(c, SERVER_NAME, strlen(SERVER_NAME), SERVER_PORT, strlen(SERVER_PORT), 1.0, 1);

    object_set.alloc = NULL;
    object_set.store = result_store;
    object_set.free = NULL;
    object_set.arg = NULL;
    object_get.alloc = alloc_value;
    object_get.store = result_store;
    object_get.free = free_value;
    object_get.arg = &result;

    for (n = 0; n < 30000; n++) {
        client_reset(c, &object_set, 1);
        client_prepare_set(c, CMD_SET, 0, KEY, strlen(KEY), 0, 0, VALUE, strlen(VALUE));
        client_execute(c);
        client_reset(c, &object_get, 0);
        client_prepare_get(c, CMD_GET, 0, KEY, strlen(KEY));
        client_execute(c);
    }

    free(result);

    return 0;
}
計測結果は

libmemcached: 6.953125

CMF: 5.984375

となりました。get/setのループだけなのにCMFは速いですね。
Perlに依存していないので、通常アプリケーションでも問題なく使えるかと思います。
memcachedと通信するC言語で作ったプログラムのパフォーマンスが悪いと思われたならば、一度試して見られてはどうでしょうか?
Posted at 15:11 in ソフトウェア::lang::c
Tagged as: c, memcached
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2008/09/26


このエントリーをはてなブックマークに追加
なんかlibmemcachedはMLにパッチ送っても反応無いし、Cache::Memcached::Fastの方が速いという噂なので、Cache::Memcached::FastをWindowsに移植してみた。
http://svn.coderepos.org/share/lang/perl/Cache-Memcached-Fast-0.12/
オリジナルからの差分は
svn diff -r19958
で取得して下さい。
リポジトリを作成し直しました。最新取得しなおして下さい。
チェックアウトしてそのままビルドして頂いてもいいです。libmemcachedはgccとVisual Studioをサポートしましたが、今回はgcc限定にしました。
よろしければ、どうぞ。
Posted at 19:15 in ソフトウェア::lang::perl
Tagged as: memcached, perl
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip


このエントリーをはてなブックマークに追加
tokuhiromさんやkazeburoさんの言うように私の所もFastが速かった。
perl の memcached libraries の速度検証 - TokuLog 改めB日記

ワタシの環境だと、Cache::Memcached::Fast の方が速いようだ。

個人的には、 mixi のような大きなサービスでの採用実績があり、速度的にも速い C::M::Fast が現時点ではいい選択肢だとおもう。C::M::libmemcached は、まだまだ開発発展途上な感じなので、もうちょい落ち着くまでは仕事では使えない印象だし。

http://d.hatena.ne.jp/tokuhirom/20080926/1222408445

結果は以下の通り。
Module Information:
 + Cache::Memcached => 1.24
 + Cache::Memcached::Fast => 0.12
 + Cache::Memcached::libmemcached => 0.02008
 + Memcached::libmemcached => 0.2101

Library Information:
 + libmemcached => 0.24

Server Information:
 + localhost:11211 => 1.2.1

Options:
 + Memcached server: localhost:11211
 + Include no block mode (where applicable)? :NO

Prepping clients...

==== Benchmark "Simple get() (scalar)" ====
                  Rate perl_memcached   libmemcached memcached_fast
perl_memcached  2319/s             --           -83%           -86%
libmemcached   13387/s           477%             --           -18%
memcached_fast 16244/s           601%            21%             --
==== Benchmark "Simple get_multi() (scalar)" ====
                 Rate perl_memcached   libmemcached memcached_fast
perl_memcached  664/s             --           -81%           -91%
libmemcached   3509/s           428%             --           -52%
memcached_fast 7306/s          1000%           108%             --
==== Benchmark "Serialization with get()" ====
                  Rate perl_memcached   libmemcached memcached_fast
perl_memcached  2104/s             --           -77%           -81%
libmemcached    8964/s           326%             --           -19%
memcached_fast 11114/s           428%            24%             --
==== Benchmark "Simple get() (w/compression)" ====
                 Rate perl_memcached   libmemcached memcached_fast
perl_memcached  972/s             --           -43%           -43%
libmemcached   1706/s            75%             --            -1%
memcached_fast 1720/s            77%             1%             --
==== Benchmark "Simple set() (scalar)" ====
                  Rate perl_memcached   libmemcached memcached_fast
perl_memcached  5316/s             --           -55%           -71%
libmemcached   11851/s           123%             --           -36%
memcached_fast 18389/s           246%            55%             --
==== Benchmark "Simple set() (w/seriale)" ====
                 Rate perl_memcached   libmemcached memcached_fast
perl_memcached 3190/s             --           -49%           -52%
libmemcached   6201/s            94%             --            -7%
memcached_fast 6668/s           109%             8%             --
==== Benchmark "Simple set() (w/compress)" ====
                Rate perl_memcached   libmemcached memcached_fast
perl_memcached 315/s             --            -2%            -8%
libmemcached   322/s             2%             --            -6%
memcached_fast 342/s             8%             6%             --
Cache::Memcached::Fastのwin32版は、まだ汚いポーティングなので、後日codereposにあげる予定。
Posted at 17:51 in ソフトウェア::lang::perl
Tagged as: memcached, perl
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2008/09/25


このエントリーをはてなブックマークに追加
オリジナルはperl版でGTKを使ったUIになっています。
memcachedclient-perl
これを、python(pygtk)、ruby(ruby-gnome2)、lua(lua-gtk)、C(GTK)、java(Swing)に移植してみた。
オリジナルから小さいコードなので、簡単なものですが...
興味のある方は、この辺を覗いて下さい。
Posted at 10:23 in ソフトウェア
Tagged as: memcached
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip