最速KVS研究第一弾(memcached/Tokyo Tyrant/Kyoto Tycoon)

TokyoとKyotoについては,オンメモリ・ディスクベースの両方を測定しましたが,memchacedが最速でした.強し!

memcachedTokyo Tyrantについては,いわゆるmemcachedプロトコルが使えるので,memcachedプロトコルでsetとgetをしました.
プログラムはやはりC言語バインディングが最速だろうということで,こんな感じです.

#include <libmemcached/memcached.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <md5.h>
#include <omp.h>

void set_key_val(MD5_CTX *con, int seed, char *key, char *val)
{
  sprintf(key,"stress-test-%010d",seed);
  sprintf(val,"%010d",seed);
  MD5Init(con);
  MD5Update(con, key, strlen(key));
  MD5End(con, key);
}
#define MAX 100000
int main(int argc, char *argv[])
{
  int suc = 0, i;
  time_t start, set_end, get_end;
  int n = omp_get_max_threads();
  memcached_st **pool = (memcached_st **)malloc(sizeof(memcached_st *)*n);
  for(i=0;i<n;++i) // スレッド数だけコネクションを張って使いまわす
  {
    memcached_server_st *servers;
    servers= memcached_servers_parse(argv[1]);
    pool[i] = memcached_create(NULL);
    memcached_server_push(pool[i], servers);
    memcached_server_list_free(servers);
  }
  start = time(NULL);
#pragma omp parallel for
  for(i=0;i<MAX;++i)
  {
    memcached_st *memc = pool[(int)i/(MAX/n)];
    memcached_return rc;
    MD5_CTX con;
    char key[33], val[11];
    set_key_val(&con, i, key, val);
    /* printf("%s:%s\n",key,val); */
    rc= memcached_set(memc,key, strlen(key), val, strlen(val), 0, 0);
    if (rc != MEMCACHED_SUCCESS)
    {
      fprintf(stderr, "memset: %s: memcache error %s",key, memcached_strerror(memc, rc));
      if (memc->cached_errno)
      fprintf(stderr, " system error %s", strerror(memc->cached_errno));
      fprintf(stderr, "\n");
    }else
    {
      ++suc;
    }
  }
  set_end = time(NULL);
#pragma omp parallel for
  for(i=0;i<MAX;++i)
  {
    memcached_return rc;
    memcached_st *memc = pool[(int)i/(MAX/n)];
    MD5_CTX con;
    char key[33], *val, should_be[11];
    size_t val_len;
    uint32_t flags;
    set_key_val(&con, i, key, should_be);
    val = memcached_get(memc, key, strlen(key), &val_len, &flags, &rc);
    if (rc != MEMCACHED_SUCCESS)
    {
      fprintf(stderr, "memget: %s: memcache error %s", key, memcached_strerror(memc, rc));
      if (memc->cached_errno)
      fprintf(stderr, " system error %s", strerror(memc->cached_errno));
      fprintf(stderr, "\n");
      fprintf(stderr, "\n");
    }else if(strncmp(val, should_be, val_len) == 0)
    {
      ++suc;      free(val);
    }else
    {
      fprintf(stderr, "value should be [%s] but returned [%s]\n",should_be, val);
      free(val);
    }
  }
  for(i=0;i<n;++i)
  {
    memcached_free(pool[i]);
  }
  get_end = time(NULL);
  printf("try=%d success=%d ratio=%f\n",MAX,suc/2,(double)suc/2/MAX*100.0);
  printf("set=%d[sec], get=%d[sec], total=%d[sec]\n", set_end-start, get_end-set_end, get_end-start);
  return(0);
}

若干冗長ですが,お試しなので勘弁して下さいまし.
コンパイル

> c++ -fopenmp memgetset-bench.c -o memgetset-bench -I/usr/local/include -L/usr/local/lib -lPocoNet -lmd -lmemcached

環境変数でスレッド数を制御.

> setenv OMP_NUM_THREADS 1

Tokyo Tyrantのオンメモリハッシュは,それがデフォルトなのでポートだけ指定して立ち上げる感じ.

> ./ttserver -port 11211

Tokyo Tyrantでディスクベースハッシュのテストをする時は,ファイル名を指定.

> ./ttserver -port 11211 /tmp/tt.tcsh


Kyoto Tycoonについては,HTTPベースの独自プロトコルなので,それに合わせた実装をしました.

#include <sstream>
#include <iostream>
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/StreamSocket.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include "Poco/StreamCopier.h"

using namespace std;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <md5.h>
#include <omp.h>
void set_key_val(MD5_CTX *con, int seed, char *key, char *val)
{
  sprintf(key,"stress-test-%010d",seed);
  sprintf(val,"%010d",seed);
  MD5Init(con);
  MD5Update(con, key, strlen(key));
  MD5End(con, key);
}
#define MAX 100000
int main(int argc, char *argv[])
{
  int suc = 0, i;
  time_t start, set_end, get_end;
  int n = omp_get_max_threads();
  vector<bool> pool_used(n, false);
  vector<Poco::Net::HTTPClientSession *> pool(n); 
  for(vector<Poco::Net::HTTPClientSession *>::iterator i=pool.begin();
       i!=pool.end();++i)  // スレッド数だけコネクションを張って使いまわす
  {
    *i = new Poco::Net::HTTPClientSession(argv[1], 11211);
  }
  start = time(NULL);
#pragma omp parallel for shared(suc,i)
  for(i=0;i<MAX;++i)
  {
    Poco::Net::HTTPClientSession *client=pool.at((int)(i/(MAX/n)));
    MD5_CTX con;
    char key[33], val[11];
    set_key_val(&con, i, key, val);
    Poco::Net::HTTPResponse response;
    Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, "/rpc/set",Poco::Net::HTTPMessage::HTTP_1_1);
    client->setKeepAlive(true);
    request.setContentType("text/tab-separated-values");
    request.setHost(client->getHost(), client->getPort());
    request.setContentLength(strlen(key)+strlen(val)+12);
    client->sendRequest(request)<<"key\t"<<key<<"\n"<<"value\t"<<val<<"\n";
    // request header
//    request.write(cout);
    istream &res =  client->receiveResponse(response);
    char *resbody = new char[len+1];
    // response headers
//    cout<<"response headers"<<endl;
//    response.write(cout);
    res.read(resbody,len);
//    cout<<resbody<<endl;
    if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_OK)
    {
      fprintf(stderr, "set: %s: kyoto error %s[%s] status=%03d",
            key, resbody, response.getReason().c_str(), response.getStatus());
      fprintf(stderr, "\n");
    }else
    {
      ++suc;
    }
    delete[] resbody;
    client = NULL;
//cout<<client->getTimeout().totalSeconds()<<endl;
  }
  set_end = time(NULL);
#pragma omp parallel for
  for(i=0;i<MAX;++i)
  {
    Poco::Net::HTTPClientSession *client=pool.at((int)(i/(MAX/n)));
    MD5_CTX con;
    char key[33], val[11];
    set_key_val(&con, i, key, val);
    Poco::Net::HTTPResponse response;
    Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, "/rpc/get",Poco::Net::HTTPMessage::HTTP_1_1);
    client->setKeepAlive(true);
    request.setContentType("text/tab-separated-values");
    request.setHost(client->getHost(), client->getPort());
    request.setContentLength(strlen(key)+5);
    ostream &req = client->sendRequest(request);
    req <<"key\t"<<key<<"\n";

    // request header
    //request.write(cout);

    istream &res =  client->receiveResponse(response);
    int len = response.getContentLength();
    char *resbody = new char[len+1];
    // response headers
    //response.write(cout);
    res.read(resbody,len);
    *(resbody+len) = '\0';
    // response body
    //cout<<resbody<<endl;
    if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_OK)
    {
      fprintf(stderr, "get: %s: kyoto error %s[%s] status=%03d", key, resbody, response.getReason().c_str(),response.getStatus());
      fprintf(stderr, "\n");
    }else
    {
      ++suc;
    }
    delete[] resbody;

  }
  get_end = time(NULL);
  printf("try=%d success=%d ratio=%f\n",MAX,suc/2,(double)suc/2/MAX*100.0);
  printf("set=%d[sec], get=%d[sec], total=%d[sec]\n", set_end-start, get_end-set_end, get_end-start);
  return(0);

コンパイル.libmemcachedを使っていないので,そのオプションだけ無い感じ.

> c++ -fopenmp memgetset-bench.cc -o memgetset-bench -I/usr/local/include -L/usr/local/lib -lPocoNet -lmd

同じく環境変数でスレッド数を制御.

> setenv OMP_NUM_THREADS 1

Kyoto Tycoonもデフォルトはオンメモリハッシュなので,そのまま立ち上げる感じ.

> ./ktserver -port 11211 -lz

ディスクベースハッシュのテストをする時は,ファイル名を指定.

> ./ktserver -port 11211 -lz /tmp/kt.kch

結果をグラフ化.
まずKey-Valueのset.

次にget.

getとsetの時間を足したトータルの処理時間.

分かることは,

  1. memcachedがやはり一番高速
  2. Tokyo TyrantとKyoto Tycoonは,メモリでもディスクでも余り速度は変わらない(=永続化に良い性能)
  3. Kyoto Tycoonは処理速度を犠牲にして,拡張性や使いやすさを実現していると思われる

などです.