最速KVS研究第一弾(memcached/Tokyo Tyrant/Kyoto Tycoon)
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は処理速度を犠牲にして,拡張性や使いやすさを実現していると思われる
などです.