2007年12月19日

- google-perftoolsを別のCPUに移植してみた

今更ながらyupo先生もgoogle perf toolsについて書かれているのを発見。はてブ万歳。LDR見落としたっぽいな・・・。

2007年12月17日

GooglePerfToolsの使い方

- 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日

epoll(2)とselect(2)の計算量

研究室で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日

close(2) while select(2)ing

昨日ちゅんさんと話していた話題。select(2)中に監視対象のディスクリプタを別スレッドからclose(2)したらどうなるの?epoll(2)だと?

以下のような事を試してみた。

thread1             thread2
--------------------------------
(r,w) <-pipe()
select(fdset = {r})
                    close(r)
                    close(w)
                    (r2, w2) <- pipe() // 同じfdになる
                    write(w2)

select(2)版コード

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;
}

epoll(2)版コード

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日

libaio(Linuxの非同期I/Oライブラリ)の使い方

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の使い方が解説してあるところがなかったので、布教も兼ねて書いてみる。

libaio(Linuxの非同期I/Oライブラリ)の使い方の続きを読む

2007年05月27日

flock(2)はスレッドも排他するか?(2)

flock(2)ではなくlockf(3)だとメールを頂いた。

確かにlockf(3)に変えてみると(lenは0)、排他してくれない。なんで挙動が違うんだ・・・。

2つの違いが分かったらまた書きます。

2007年05月26日

flock(2)はスレッドも排他するか?

以前人づてに、flock(2)がプロセス間排他はするけどスレッド間排他はしないという話を聞いた。

これが本当だと、今まで自分が書いたマルチスレッドアプリケーションのファイルを扱う部分の根幹が揺らいでしまう。特にファイルが壊れたりするような現象は見られなかったので今まで放置してきたが、非常に心配になってきたので調べてみた。

flock(2)はスレッドも排他するか?の続きを読む

2007年05月18日

splice(2)

Linux 2.6.17より導入された新しいシステムコールである「splice(2)」を使ったファイルコピープログラムを作ってみました。

参考: C言語: UNIX最速ファイルコピー
参考: splice(2) - splice data to/from a pipe

splice(2)の続きを読む

2007年02月18日

pthread_mutex_unlock忘れ

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_ERRORCHECK_NP

pthreadのmutexではpthread_mutex_initでattributeが付けられますが、PTHREAD_MUTEX_ERRORCHECK_NPというattributeが有るのを見つけたので適当に浅追いしてみました。

PTHREAD_MUTEX_ERRORCHECK_NPの続きを読む

2007年01月30日

Multicore Programming Primerが面白すぎる

MITの「Multicore Programming Primer」というコースの資料が面白すぎます。

Multicore Programming Primer

Multicore Programming Primerが面白すぎるの続きを読む

2007年01月23日

valgrindのページ

1年ほど前に公開したvalrindのページだが、日増しに国内メーカーさんからのアクセスが増している。参照元URLを見ると社内wikiとか。

大分普及してきたかな。

2006年11月29日

Asyncronous I/O

ソース。

AsyncIOについて(その1)
AsyncIOについて(その2)

またあちこちのBlogを見る限りNonBlockingI/OやNonBlockingI/O+シグナルとAIOが混同されている気がしたので,それら整理してみたい.

大体以下のような理解でいいのでしょうかね。もしかしたらきっちりした定義が有るのかもしれませんが。

Asyncronous I/Oの続きを読む

安全なCFLAGS

ソース

うーむ、参考になる。

加えてFedoraCore6上のgcc 4.1.1では-ftrapvをつけると、あるソースをコンパイル中にSegmentation Faultしてしまう事を確認しました。ちょっとここらで安全なCFLAGというのをgcc projectの人に書いてもらいたい所だなぁ。

2006年11月13日

Async IO on lighttpd

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年10月03日

スレッド生成コスト

計測してみた。

スレッド生成コストの続きを読む

2006年09月26日

glibc malloc

革命の日々!カーネル読書会で講演してきました

The 67th kernel reading party

kosakiさんのglibc mallocに関する講演。楽しすぎて、1時間半も有るのに気づいたら見終わってしまった。個人的にはマルチスレッド環境におけるロックの取り方 -> アリーナの確保周りが凄い参考になった。ってかカーネル読書会はツッコミが鋭くて素晴らしい会すぎる。MLに登録したので気になるネタが会ったら行ってみよー。

さて、H木先生のテストの日程が遂に決まって29日に。1ヶ月ほど前から昔のサークルの仲間と温泉行く約束してたのですが、断るハメに。もっと早く決めて頂きたかったですよ、ほんと...。

2006年09月23日

GDB: Debugging programs with multiple threads

Debugging programs with multiple threads

GDBでマルチスレッドプログラムをデバッグするのはどうやるのだろう、と思っていたらこんなのが。「strace -f」というのも最近教えて頂いて、如何に自分がツールを使いこなせていないか身にしみた。やっぱりgdbとgccは一度ドキュメントに全部目を通すべきだな。もっと「早く知ってれば...」的なのが多すぎる。

マルチスレッドでのバックトレースの吐き方はここらへんのソースを読めば分かりそう。

2006年09月18日

liblog

Replay Debugging for Distributed Applications

Presentation

Dennis Geels

分散ソフトウェアを開発している時に一番頼りになるのは、というか唯一頼りになるのはログ。バグが起こったときには、それこそ行単位にログを吐かせまくって原因を特定する。ローカルに吐かせたものを見るのはどうしても面倒なので、中央にログサーバーを立てて集中的にログを見れるようにもしてみた。しかし台数が増えると色々なサーバーのログが入り混じってしまうので、結局見るのはローカルに吐かせたログになってしまう。

liblogはなんとログからその時の状況をreplayするという凄いハック。thread, signal, malloc, network等の状況も再現してくれる。thread対応には独自のスケジューラーを作ってみたり、gdbをインターフェースにしてみたりと色々ハッカーすぎる技術が詰まっている。しかし肝心のliblogのページが落ちてるのが悲しすぎる。

個人的にはlibcの関数をrecordしてくれるだけでも十分嬉しい。straceはログ吐き過ぎなので、10時間後に止まるバグなんかを突き止めるにはちょっと向いてない...。しかし今思い出して見ると、Jockeyも同じような事が出来るのか。こっちはソースが読めるのでちょっと見てみよう。

こういう事してるから課題が進まない。

2006年09月15日

Asynchronous IO

例の自前のライブラリに非同期I/Oを組み込もうとしてみたのだが、どうも上手く行かない。多少パフォーマンスはあがるのだけど、エラー処理どうするの?とか。

世界中でこいつを有効に使えているソフトウェアって有るんですかねぇ...。

Bigtable

Databaseにお熱。Bigtableとか見せつけられたら作りたくなるよなぁ。このGoogle特有の割り切り方が良い。そして競合してみたくなる。色々資料読んだり、nvacaさんの紹介でXerialの中の人のお話を聞かせて頂いたり。

地下クラスタ使って色々楽しいことしたいけど、結局ルート権が無いから結構不便...。I川研の人くれないかなぁ。ライブラリとか入れるのとか面倒す。

しかし複数台のマシンを使ったプログラミングというのは楽しすぎる。身辺が落ち着いたら & もうちょい調べたらちょっと簡単なのを実装してみる。負荷テストをクリアした超強力なネットワークライブラリとスレッドライブラリが手元に有るので本題から入れそう。3~6ノードぐらいで最高性能が発揮できてwebのバックエンドに向いている奴を作ってみるというのが、手頃でしかも需要がありそうで良いかなぁ。

とかまぁ妄想中。

2006年07月30日

C言語で実行時バックトレース

glibcにはbacktrace()という便利な関数が有るらしいのですが、あえてその中身を自分で実装してしまおうという暇潰しをしてみました。

C言語で実行時バックトレースの続きを読む