2007年12月19日
お
- google-perftoolsを別のCPUに移植してみた
今更ながらyupo先生もgoogle perf toolsについて書かれているのを発見。はてブ万歳。LDR見落としたっぽいな・・・。
- Permalink
- by
- at 06:52
- Comments (0)
- Trackbacks (0)
2007年12月19日
- google-perftoolsを別のCPUに移植してみた
今更ながらyupo先生もgoogle perf toolsについて書かれているのを発見。はてブ万歳。LDR見落としたっぽいな・・・。
2007年12月17日
- google-perftools - Fast, multi-threaded malloc() and nifty performance analysis tools
- 肥え続けるTomcatと胃を痛めるトラブルハッカー
- ローテクなメモリ使用量監視方法
PFIでは毎週1人適当な話題で発表しているのですが、この間「GooglePerfToolsの使い方」という軽いお題で発表した資料を公開してみます。特にC++で長期運用中のメモリリークに苦しんでおられる方は必見。基本的にドキュメントの日本語訳ですがね!SlideShareだとなぜか図がずれる。元ファイルはこちら。
google perf toolsのheap profiler機能は秀逸で、ある時点とある時点を比較して、どの関数がどのぐらいmallocしてメモリ使用量を増加させたかというのが一瞬で分かります。またそれを可視化する事もできます。さらにランタイムオーバーヘッドも少ない(間隔を指定できる)ので、運用しながらログを取ることも許容の範囲内だと思います。
性能に関してもSTLを使用しているアプリケーションに関しては細かいアロケーションが鬼のように発生するので、glibc mallocを使用したmultithreadアプリケーションの場合、malloc内部のロックがボトルネックになる場合があります。これはSolarisのplockstate(dtraceのwrapperコマンド)等により実際に確認することが出来ます。tcmallocはその辺りを解決しているので、Makefile.amに-ltcmallocを付け加えるだけで簡単にアプリケーションのスループットが向上しました。
また、glibc標準のアロケーターだとメモリフラグメンテーションで予想以上のメモリを食ってしまう事が有るようです。某検索エンジンでも稀にその状況が発生する事が有って、アプリケーション側のバグだと思い3ヶ月間ずっとコードとにらめっこしていたのですが、結局アロケーターが悪いという結論に陥りました。もっと早くGooglePerfToolsを知っていたら胃に穴が開くことも無かったでしょう。
という訳でみんなGooglePerfTools使うといいよ!
Google => アロケーター作ってる会社
中の人ありがとう!
2007年11月20日
研究室でid:yama6がepollとか言っていて、mixi Engineers' Blog さんの「Linux Programming、epollの話」を思い出した。
パフォーマンスの方はselect(2)とpoll(2)のtime complexityがO(n)に対しepollはO(1)と無視のできない性能の差を実現しています。
これこれ、書こうとして忘れてた。僕の理解だとepoll(2)はO(n)でselect(2)がO(n^2)です。この差はsignificantですよ!
例えば1万個のソケットを管理しているとします。で、ソケットが1個づつ順番に読み込み可能になるという最悪のシチュエーションを考えて見ます。select(2)だと10000 * 10000のループが回ります。epoll(2)だと10000 * 1回のループが回ります。
そういう上手いペースで接続をする方法を思いつかないので、実データで示せないのが申し訳ないですが。libeventのグラフもO(n ^ 2)の曲線になってませんな...。
2007年11月16日
昨日ちゅんさんと話していた話題。select(2)中に監視対象のディスクリプタを別スレッドからclose(2)したらどうなるの?epoll(2)だと?
以下のような事を試してみた。
thread1 thread2
--------------------------------
(r,w) <-pipe()
select(fdset = {r})
close(r)
close(w)
(r2, w2) <- pipe() // 同じfdになる
write(w2)
void* fd_consumer(void *writefd_){
int* pipefd = (int*)writefd_;
sleep(1);
close(pipefd[0]);
close(pipefd[1]);
cerr << "close(2) pipe" << endl;
sleep(1);
if(pipe(pipefd)){ perror("Failed to make pipe"); exit(1); }
cerr << "pipe(2) in another thread: " << pipefd[0] << ", " << pipefd[1] << endl;
sleep(1);
cerr << "write(2) to pipe" << endl;
char buf[] = "a";
write(pipefd[1], buf, 1);
return NULL;
}
int main(void){
int pipefd[2];
if(pipe(pipefd)){
perror("failed to make pipe");
exit(1);
}
cerr << "pipe(2): " << pipefd[0] << ", " << pipefd[1] << endl;
fd_set rfds;
fd_set wfds;
fd_set efds;
pthread_t thread;
pthread_create(&thread, NULL, fd_consumer, (void *)pipefd);
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
FD_SET(pipefd[0], &rfds);
FD_SET(pipefd[0], &wfds);
FD_SET(pipefd[0], &efds);
struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0;
int r = select(pipefd[0]+1, &rfds, &wfds, &efds, &tv);
if(r < 0){
cerr << "select(2) error" << endl;
} else if (r == 0){
cerr << "select(2) timedout" << endl;
}else{
cerr << "select(2) catched " << r << " elements" << endl;
}
if(FD_ISSET(pipefd[0], &rfds))
cerr << "pipefd[0] set(rfds)" << endl;
if(FD_ISSET(pipefd[0], &wfds))
cerr << "pipefd[0] set(wfds)" << endl;
if(FD_ISSET(pipefd[0], &efds))
cerr << "pipefd[0] set(efds)" << endl;
return 0;
}
void* fd_consumer(void *writefd_){
int* pipefd = (int*)writefd_;
sleep(1);
close(pipefd[0]);
close(pipefd[1]);
cerr << "close(2) pipe" << endl;
sleep(1);
if(pipe(pipefd)){ perror("Failed to make pipe"); exit(1); }
cerr << "pipe(2) in another thread: " << pipefd[0] << ", " << pipefd[1] << endl;
sleep(1);
cerr << "write(2) to pipe" << endl;
char buf[] = "a";
write(pipefd[1], buf, 1);
return NULL;
}
int main(void){
int epfd = epoll_create(10);
int pipefd[2];
if(pipe(pipefd)){
perror("failed to make pipe");
exit(1);
}
cerr << "pipe(2): " << pipefd[0] << ", " << pipefd[1] << endl;
struct epoll_event epev;
memset(&epev, 0, sizeof(epev));
epev.events = EPOLLIN;
epev.data.fd = pipefd[0];
epoll_ctl(epfd, EPOLL_CTL_ADD, pipefd[0], &epev);
pthread_t thread;
pthread_create(&thread, NULL, fd_consumer, (void *)pipefd);
struct epoll_event events[10];
int r = epoll_wait(epfd, events, 10, 5000);
if(r < 0){
cerr << "epoll(2) error" << endl;
} else if (r == 0){
cerr << "epoll(2) timedout" << endl;
}else{
cerr << "epoll(2) catched " << r << " elements" << endl;
}
for(int i=0; i<r; i++){
if(events[i].events & EPOLLIN)
cerr << "EPOLLIN" << endl;
if(events[i].events & EPOLLOUT)
cerr << "EPOLLOUT" << endl;
if(events[i].events & EPOLLERR)
cerr << "EPOLLERR" << endl;
if(events[i].events & EPOLLHUP)
cerr << "EPOLLHUP" << endl;
}
return 0;
}
mac% g++ seltest.cpp -Wall; time ./a.out pipe(2): 3, 4 select(2) error pipefd[0] set(rfds) pipefd[0] set(wfds) pipefd[0] set(efds) ./a.out 0.00s user 0.01s system 0% cpu 1.011 total
linux% g++ -Wall seltest.cpp -lpthread; time ./a.out pipe(2): 3, 4 close(2) pipe pipe(2) in another thread: 3, 4 write(2) to pipe select(2) catched 1 elements pipefd[0] set(rfds) ./a.out 0.00s user 0.00s system 0% cpu 5.007 total
linux% g++ -Wall epolltest.cpp -lpthread; time ./a.out pipe(2): 4, 5 close(2) pipe pipe(2) in another thread: 4, 5 write(2) to pipe epoll(2) timedout ./a.out 0.00s user 0.00s system 0% cpu 5.007 total
select(2)だとMacOS XとLinuxで挙動が違う。未定義動作?
Macの方の動作を意図してselect(2)を使ったプログラムを書いていたので、ちょっと困った。代替となるportableな方法無いかなぁ。
2007年06月05日
Linuxで非同期I/Oを行うためのライブラリ「libaio」の使い方を書いてみる事にする。少し昔の話になるが、lighttpdが使用し、スループットを80%も上げたらしい。
TOEFLに向けて転置ファイルについての論文(Inverted files for text search engine [moffat 06])でReading対策をしていたところ、意外とスニペット(検索にヒットした箇所の前後の文章)を作るところが時間がかかるという事を教えてもらったので、適当にそれを例題にしてみる。具体的には以下のようなコードを非同期I/Oを使用して速くなるかどうか見てみる。
for (unsigned int i = 0; i < files.size(); i++) {
FILE* fp = fopen(files[i].c_str(), "rb");
if (fp == NULL) continue;
fseek(fp, offsets[i], SEEK_SET);
char buf[64];
size_t nread = fread(buf, 1, 64, fp);
fclose(fp);
}
ファイルリストとそれに付随するオフセットのリストが与えられた時、各ファイルの指定位置から64 byteづつ読み込むようなプログラムだ。
色々高速化する手段は有るが、日本語でlibaioの使い方が解説してあるところがなかったので、布教も兼ねて書いてみる。
2007年05月26日
以前人づてに、flock(2)がプロセス間排他はするけどスレッド間排他はしないという話を聞いた。
これが本当だと、今まで自分が書いたマルチスレッドアプリケーションのファイルを扱う部分の根幹が揺らいでしまう。特にファイルが壊れたりするような現象は見られなかったので今まで放置してきたが、非常に心配になってきたので調べてみた。
2007年05月18日
Linux 2.6.17より導入された新しいシステムコールである「splice(2)」を使ったファイルコピープログラムを作ってみました。
参考: C言語: UNIX最速ファイルコピー
参考: splice(2) - splice data to/from a pipe
2007年02月18日
static int
func1(void)
{
pthread_mutex_lock(&mutex);
int r = func2();
if (is_error(r)) {
return -1;
}
pthread_mutex_unlock(&mutex);
return 0;
}
この手のバグは本当に取りづらい。具合が悪い事に、あるサービス用のクライアントライブラリだったのでこのコードを使っている全てのインスタンスが数十時間後にdeadlockで止まった。
func1がネットワークがらみのコードだったので、偶然ネットワークが不調になってエラーになり、次にこの関数に突入したときに固まったようである。
今回は人の目にお世話になって、debug出来た。なんでこの場所に限ってRAIIを使わなかったのだろう(自分)...。
2007年02月15日
pthreadのmutexではpthread_mutex_initでattributeが付けられますが、PTHREAD_MUTEX_ERRORCHECK_NPというattributeが有るのを見つけたので適当に浅追いしてみました。
2007年01月30日
MITの「Multicore Programming Primer」というコースの資料が面白すぎます。
2007年01月23日
1年ほど前に公開したvalrindのページだが、日増しに国内メーカーさんからのアクセスが増している。参照元URLを見ると社内wikiとか。
大分普及してきたかな。
2006年11月29日
ソース。
AsyncIOについて(その1)
AsyncIOについて(その2)
またあちこちのBlogを見る限りNonBlockingI/OやNonBlockingI/O+シグナルとAIOが混同されている気がしたので,それら整理してみたい.
大体以下のような理解でいいのでしょうかね。もしかしたらきっちりした定義が有るのかもしれませんが。
うーむ、参考になる。
加えてFedoraCore6上のgcc 4.1.1では-ftrapvをつけると、あるソースをコンパイル中にSegmentation Faultしてしまう事を確認しました。ちょっとここらで安全なCFLAGというのをgcc projectの人に書いてもらいたい所だなぁ。
2006年11月13日
lighty's lifeによると、次期lighttpdはaioを使って80%ほどスループットが向上したようである。
The idea is:
1. create a buffer in /dev/shm and mmap() it
2. start a async read() from the source file to the mmap() buffer
3. wait until the data is ready
4. use sendfile() to send the data from /dev/shm to the network socket
Important for the performance: the data is never copied into user space. We only move it from one side of the kernel to the other side.
賢い。確かに早くなるのは分かるんだけど、エラー処理の部分が気になるなぁ。コード見てみる。しかしまずlibaio.hというのが何処にあるか分からんぞ...。このライブラリはどこから来ておるのだ...。
2006年09月26日
kosakiさんのglibc mallocに関する講演。楽しすぎて、1時間半も有るのに気づいたら見終わってしまった。個人的にはマルチスレッド環境におけるロックの取り方 -> アリーナの確保周りが凄い参考になった。ってかカーネル読書会はツッコミが鋭くて素晴らしい会すぎる。MLに登録したので気になるネタが会ったら行ってみよー。
さて、H木先生のテストの日程が遂に決まって29日に。1ヶ月ほど前から昔のサークルの仲間と温泉行く約束してたのですが、断るハメに。もっと早く決めて頂きたかったですよ、ほんと...。
2006年09月23日
Debugging programs with multiple threads
GDBでマルチスレッドプログラムをデバッグするのはどうやるのだろう、と思っていたらこんなのが。「strace -f」というのも最近教えて頂いて、如何に自分がツールを使いこなせていないか身にしみた。やっぱりgdbとgccは一度ドキュメントに全部目を通すべきだな。もっと「早く知ってれば...」的なのが多すぎる。
マルチスレッドでのバックトレースの吐き方はここらへんのソースを読めば分かりそう。
2006年09月18日
Replay Debugging for Distributed Applications
分散ソフトウェアを開発している時に一番頼りになるのは、というか唯一頼りになるのはログ。バグが起こったときには、それこそ行単位にログを吐かせまくって原因を特定する。ローカルに吐かせたものを見るのはどうしても面倒なので、中央にログサーバーを立てて集中的にログを見れるようにもしてみた。しかし台数が増えると色々なサーバーのログが入り混じってしまうので、結局見るのはローカルに吐かせたログになってしまう。
liblogはなんとログからその時の状況をreplayするという凄いハック。thread, signal, malloc, network等の状況も再現してくれる。thread対応には独自のスケジューラーを作ってみたり、gdbをインターフェースにしてみたりと色々ハッカーすぎる技術が詰まっている。しかし肝心のliblogのページが落ちてるのが悲しすぎる。
個人的にはlibcの関数をrecordしてくれるだけでも十分嬉しい。straceはログ吐き過ぎなので、10時間後に止まるバグなんかを突き止めるにはちょっと向いてない...。しかし今思い出して見ると、Jockeyも同じような事が出来るのか。こっちはソースが読めるのでちょっと見てみよう。
こういう事してるから課題が進まない。
2006年09月15日
例の自前のライブラリに非同期I/Oを組み込もうとしてみたのだが、どうも上手く行かない。多少パフォーマンスはあがるのだけど、エラー処理どうするの?とか。
世界中でこいつを有効に使えているソフトウェアって有るんですかねぇ...。
Databaseにお熱。Bigtableとか見せつけられたら作りたくなるよなぁ。このGoogle特有の割り切り方が良い。そして競合してみたくなる。色々資料読んだり、nvacaさんの紹介でXerialの中の人のお話を聞かせて頂いたり。
地下クラスタ使って色々楽しいことしたいけど、結局ルート権が無いから結構不便...。I川研の人くれないかなぁ。ライブラリとか入れるのとか面倒す。
しかし複数台のマシンを使ったプログラミングというのは楽しすぎる。身辺が落ち着いたら & もうちょい調べたらちょっと簡単なのを実装してみる。負荷テストをクリアした超強力なネットワークライブラリとスレッドライブラリが手元に有るので本題から入れそう。3~6ノードぐらいで最高性能が発揮できてwebのバックエンドに向いている奴を作ってみるというのが、手頃でしかも需要がありそうで良いかなぁ。
とかまぁ妄想中。
2006年07月30日