TokyoとKyotoについては,オンメモリ・ディスクベースの両方を測定しましたが,memchacedが最速でした.強し!
memcachedとTokyo 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の時間を足したトータルの処理時間.

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