再利用と再実装について

2009/10/22 00:28
shunya

寒くなってきましたね.秋です.ブログを更新する時間がなかなか確保できませんな今日この頃です.

はじめに

今日は機械学習におけるソフトの「再利用」と「再実装」についておもしろい記事を見つけたので引用しながら考察したいと思います.このトピックに関しては機械学習だけに限らず,すべてのソフトにおいていえると思います.ソフトの「再利用」と「再実装」に関しては,私は学生時代から結構議論になっていましたし,そして現在ビジネスシーンでもこれについては議論になります.特にビジネスでは工数という概念があるためなるべくなら「再実装」はしたくないという前提がありますよね.

本日引用する記事は以下の記事です.

Re-implement or reuse?

Posted by Cheng Soon Ong on October 14, 2009

このブログはmloss.org(machine learning open source software)という機械学習に関する手法の実装を登録・検索できるサイト上の有識者による機械学習に関するコラムに掲載されていた記事です.ここのサイトは実装の検索だけでもかなり有用なのですが,何気にブログもおもしろい!ということを渋谷の中心で叫びたいと常々思っていました.

Why reuse and Why re-impelment

さて,上記の記事で「再実装または再利用?」というタイトルで以下のように,それぞれのメリットを論じています.

Why reuse
    ■ Standard software engineering practice 
    ■ antipattern (reinvent the wheel) 
    ■ Better than "hacked up" own version
    ■ Save time by outsourcing maintenance

Why re-implement
    ■ Existing solution is not good enough
    ■ Educational purposes
    ■ Not aware of other solution
    ■ Existing interface changes too often

Re-implement or reuse? より引用 詳しくは参照先のエントリーを参照されたし.

和訳

  • なぜ再利用するか
    • 一般的なソフトウェア工学の慣習のため
    • アンチパターン(車輪の再発明)
    • 自分で作るよりも優れているため
    • メンテナンスの時間がとられない
  • なぜ再実装するのか
    • 既存のソリューションは十分ではない
    • 教育的な目的
    • すでに存在するソリューションの存在に気づいていない

考察

当たり前といってしまえば当たり前のことのなかもしれないですが,今後の自分のためにもこのトピックに関して自分なりの考えをまとめておきます.

ある問題を解決するときには,考えなしにすぐに再実装するのではなく,再実装するにせよ再利用するにせよ,必ず既存の実装について調査をしなくてはならないと思っています.例えば,既存の実装では,マシンスペックをどれぐらい要求するのか(特にメモリ使用量など),処理時間はどれぐらいかかるのか,APIは適切に実装されているかなどを調べなくてはならないと考えています.この調査をしっかりしていなければ,再実装した際に評価する指標も無いし,もしかしたら劣ったものを実装してしまう可能性もあるためです.再利用できるかを検討する際は,「自分が求める要求」と「既存の実装に足りないこと」を照らし合わせ,再実装したほうが早いか,既存の実装を修正し再利用したほうが早いかを検討します.

ちょっと中途半端ですが今後もこの話題に関しては機会があったらまとめたいと思います.この手の話題は結構ソフトウェア工学系の分野では語り尽くされているのかもしれないので,その手の本を読んだときにまた再考したいと思います.

もし,読んでくださった皆様にも意見がございましたら,お気軽にコメントくださいませ.


機械学習の勉強を始めるには

2009/10/13 00:05
shunya

thriftとかhadoopなど,何やらいろいろと手を出してしまい,ここのところブログの更新が滞ってしまっていますが,今日は前から書きたかったトピックについて自分へのメモの意味も含めて記しておきたいと思います.

はじめに

最近,といっても結構前からなのですが,海外のブログなどで「機械学習の勉強を始めるガイドライン」についてのエントリーがいくつか見られ,かつ,議論も少し盛り上がっています.僕は機械学習が好きなだけで,専門というにはほど遠いのですが,僕も一利用者としてはこのトピックに関してはとても興味があります.

機械学習というと,色々な数学的な知識が必要であったり,統計学や人工知能の知識も必要になったりしまったりと,専門的に学ぶ機会が無かった人にとっては興味が湧いてもなかなか始めるには尻込みしてしまうことかと思います.今日紹介するエントリーは,そんな方々にヒントになるような内容になっていると思います.基本的なことを体系的に説明しているのでとても質が高いです.

紹介するエントリー : Guide to Getting Started in Machin Learning

要約になりますが,上記のエントリーでは機械学習の勉強を始める流れを以下のように説明しています.

The Elements of Statistical Learning Data Mining, Inference, and Prediction.を読む

  • なんとこの本はPDFが公開されていますので参照してください.
  • この本は有名な教科書なのですが,これのチャプター1のIntroductionを読んで概要をつかむと良いと言っています.
  • Introductionには統計学習がどのような分野で応用されているかなどがまとめられています.
    • 医療分野や,マーケティング,画像処理,e-mailのスパムフィルタリングなど

Rで試す

  • The Elements of Statistical Learning Data MiningのIntroductionを読んだら,Rをダウンロードしましょう.
  • GoogleやFacebookではRがどのように活用されているかを記したエントリーのリンクがありおもしろかったです.How Google and Facebook are using R

Googleの場合

The typical workflow that Bo thus described for using R was: (i) pulling data with some external tool, (ii) loading it into R, (iii) performing analysis and modeling within R, (iv) implementing a resulting model in Python or C++ for a production environment.

上記を意訳で要約すると,GoogleのBoさんはRを以下のように使っているようです.

  1. 何らかの抽出ツールでデータを取得する
  2. Rに読み込ませる
  3. Rを使って解析したりモデルを作ったりする
  4. 結果のモデルをPythonやC++で実装する

といった感じで,つまりプロトタイプの段階でRを使って実験するようですね.

データを使う

Rで何かしらので機械学習の手法を使うには,データが必要となります.学習やテスト(分類)に使うデータをよくデータセットというのですが,これを集めるのは容易ではありません.以前にここのサイトでも紹介したデータセット集(Classicicationにおけるデータセット)をこのエントリーでも紹介しています.

UCI Machine Learning Repository: Data Sets

ここのデータセットを使ってRで遊んでみると良いでしょう.エントリーでは,先ほど示したThe Elements of Statistical Learning Data Miningを読み進めてRで実装してみると良いと言っています.

個人的には直感的に学ぶにはWekaもいいと思っています.wekaについては後述します.

Bishop本を読む

他にも機械学習の教科書は山ほどあるのですが,以下の本がおすすめだと言っています.

Pattern Recognition and Machine Learning (Information Science and Statistics)

image:1:61LLReXatVL._SL160_.jpg

この本はかなり評判がいいですよね.実は僕は図書館で必要な箇所を読んだことしかないのですが,よくまとまっていた記憶があります.これは和訳が販売されていて,これもかなり評判がいいですね.結構色々なところで読書会とかもされているようですね.僕も買わなくちゃ.Bishop先生が書いた本ということで,一部ではBishop本と呼ばれているようです.

パターン認識と機械学習 上 - ベイズ理論による統計的予測

元田 浩

image:2:41z0AD06ZaL._SL160_.jpg

授業の動画ライブラリを見る

昨今は本当に便利な時代になって,スタンフォード大学の機械学習の授業を見ることができます.

Course Machine Learning

以下が授業で使っているハンドアウトになります.

CS229 Machine Learning Course Materials

実は,これは僕もジムで筋トレしながらこつこつと見ていたのですが,とてもおもしろいです.でも,音声が英語ということもあって,英語力も要求されるため,機械学習に関して全く知識が無いと聞き取りが大変かもしれないので,英語が得意ではない方は一通り勉強し終わってから復習の意味で見ると良いかもしれません.

以上が上記のエントリーに記述されていた,機械学習の初学者のためのガイドラインとなります.

で,自分はどう思うか

で,自分もまだまだ初学者の一員ではあるのですが,自分なりに機械学習を勉強する事始めとして良いのではないかと思っていることをまとめておきます.もちろん,これは賛否両論あると思いますし,人によって効率の良い学習方法は違うと思いますので,これは一アイデアとして読んでいただけたらと思います.

まずは,興味を持った手法を徹底的に学び尽くす

私は機械学習の全体の知識をいきなりつけようとすると挫折してしまう可能性が高いと思っています.特に昨今のアルゴリズムはとても複雑なモデルを使うようになっており,理解に時間がかかるものが多くあります.そこで,私としては(当時の私は)自分がやりたいことが達成できそうなアルゴリズムを1つに絞って勉強するというスタンスをとりました.そして,そのアルゴリズムは初学者であれば単純なものが良いと思います.例えば,機械学習の分野(明確には機械学習の分野とは言えませんが)で単純なアルゴリズムで,威力があるものでは,Naive Bayesや,decision tree,KNNなどがあります.これらのアルゴリズムはデータマイニングや,パターン認識などですぐに使うことができます.人によっては式だけ眺めてうなっているよりも,作りながら悩んだ方が理解が早いことがあります.動かしながらだと直感的に理解できるんですね.もちろん,人それぞれ学習の仕方があると思うので万人には通用するとは思っていません.

そして,一つのアルゴリズムを徹底的に学ぶといっても,実は全然一つでは収まらなくなります.例えばNaive Bayesを使ってみよう!と思ってNaive Bayesの論文などを読むと,その論文の中には細かい引用がたくさんされています.そして,その引用先の論文で言っている手法を理解していないと,その論文で言っていることがわからなかったりします.結局一つの論文を読むのに,10以上の論文を読むことになったりします.だから,簡単なアルゴリズムでもちゃんと理解しようするにはとても苦労するんですね.だけど,学ぶことがたくさんあるからそういうことをしているうちにだんだん幅広い知識がついてくる,と経験上思いました.

オープンソースのライブラリから学ぶ

僕がこれらの勉強を始めた当時は,実はあまりオープンソースのライブラリの情報がそこまで出回っていませんでした.しかし,今は本当に多くの,かつ質のいいオープンソースの機械学習のライブラリがあります.教科書を見ていてよくわからない!という方は人の実装を見ながら仕組みを理解するといいと思います.数式がものすごく難しく説明されていても,ソースを見ると意外に実装は簡単だったりするものもあります,自分の力だけで実装するのもいいし,人の実装を参照しながら自分の実装をするのも良いと思います.

一例ではありますが,最近では以下のようなものがあります.(全部細かくは調査していません)

  • nltk
    • http://code.google.com/p/nltk/
    • Python
    • 自然言語処理のライブラリなのですが,機械学習のアルゴリズムも多く実装されています.
    • Pythonということもあって,処理は遅いですがコードは読みやすいのでプロトタイプとしてはおすすめです.
    • クラスタリング・分類関係だと以下のような手法が実装されています
      • decision tree
      • maximam entropy
      • naibe bayes
      • kmeans
  • weka
    • http://www.cs.waikato.ac.nz/ml/weka/
    • JAVA
    • このサイトでもちょっと紹介したwekaですが,これはJAVAで実装されたData miningツールで多くの機械学習の手法が実装されています.
    • 有名な手法はだいたい実装されています.スピードもそこそこ出るのでちょっと遊んだり実装を見るにはおすすめ.
  • Mahout
    • http://lucene.apache.org/mahout/
    • JAVA
    • Hadoop上で動く機械学習ライブラリです.これは初学のためにはお勧めできませんが,Hadoop上で動くということもあり,実用上使えると思って現在調査中です.

まとめ

今回は素人の素人による素人のための機械学習事始めということでエントリーを書かせていただきました.実は僕は勉強の仕方は「何が一番いい」っていうのは無いと思っています.でも,勉強するために手助けになるための情報は存在すると思っています.それを一度まとめておきたいなと思って,書かせていただきました.もし,こんな情報があるよ!とか,こうやると効率がいいよ!というアイデアがある方がいらっしゃいましたらご教授いただけたらと思います.

----

10/14 追記

shima_shimaさんより参考になるご意見をいただいたので追記しておきます。以下shima_shimaさんのコメントを整理して引用させていただきます。

  • Bishop本のレベル表

http://ibisforest.org/index.php?PRML/course

  • その他参考になる本
  • 強化学習なども解説されているが古い部分もある

Machine Learning (Mcgraw-Hill Series in Computer Science)

image:3:41NEYGGHM5L._SL160_.jpg
  • カーネル

カーネル多変量解析―非線形データ解析の新しい展開 (シリーズ確率と情報の科学)

image:4:51CKmDcTiyL._SL160_.jpg
  • マルコフ連鎖

計算統計 2 マルコフ連鎖モンテカルロ法とその周辺 (統計科学のフロンティア)

image:5:410B4V8HAXL._SL160_.jpg
  • 入門で優しい内容

フリーソフトでつくる音声認識システム - パターン認識・機械学習の初歩から対話システムまで

image:6:519Jh4Vxl9L._SL160_.jpg
  • Bishopレベル表の入門レベルの内容

パターン認識と学習の統計学―新しい概念と手法 統計科学のフロンティア 6

image:7:4135AXJAS9L._SL160_.jpg
  • Bishopレベル表の入門レベルの内容

データマイニングの基礎 (IT Text)

image:8:41JPn3di8sL._SL160_.jpg

多重化してThriftを使ってみた

2009/10/01 17:36
shunya

ここまでの流れ

Thriftの調査の続きです。前回のエントリー(Thriftのスピードが改善しました)では、Thriftのperlクライアントが遅いのはNagleアルゴリズムが原因ではないかという仮説をたて、そしてNagleアルゴリズムをオフにした状態で速度が向上したことを書きました。

それに対し、tokuhiromさんにより投稿されたエントリー(ThriftはThrift::BufferedTransport をつかいわすれると 147 倍遅くなってつかいものにならない)では、send(2)のバッファリングをしていないことが原因で、Thriftが吐き出すデフォルトのperlクライアントのスケルトンでは使われていない、 Thrift::BufferedTransport を使うことによってsend(2)がバッファリングされて、高速になることを示してくれました。加えてkazuhookuさんのご指摘によりread(2)もバッファリングしてないため遅いとのこと。僕の環境でもThrif::BuffredTransporを使うことによって高速化することを確認しました。

そして、「Thriftのスピードが改善しました」のエントリーに対して、はてぶコメントでkazuhookuさんに以下のコメントをいただきました。kazuhookuさん、ありがとうございます!

282QPSってむちゃくちゃ遅いと思う。てか、サーバサイドのロジックがここまで単純なら、多重化してベンチとるべき / これだとパケット流れまくるのでせめて MSG_MORE とか使うべき

http://b.hatena.ne.jp/entry/blog.broomie.net/index.cgi?id=38 より引用

MSG_MOREについて

ご指摘をいただくまで知らなかった&調べてみてわかったのですが、確かにTCP_NODELAYにするのであれば、MSG_MOREを使ったほうが良いみたいですね。冗長で恐縮なのですが、メモの意味も含めて整理すると、TCP_NODELAYというのはmanで以下のように解説されています。

% man tcp

ソケットオプション
  TCP ソケットのオプションは、オプションレベル引数に IPPROTO_TCP を指定した setsockopt(2) で設定でき、 getsockopt(2)で取得できる。さらに、ほとんどの IPPROTO_IP ソケットオプションも TCP ソケットに対して有効である。詳細は ip(7)を見よ。

TCP_NODELAY
  設定すると Nagle アルゴリズムを無効にする。すなわち、データ量が少ない場合でも各セグメントは可能な限り早く送信される。設定されていないと、送信する分だけ溜まるまでデータはバッファされ、小さなパケットを頻繁に送らずにすみ、ネットワークを有効に利用できる。このオプションは TCP_CORK により上書きされる。しかしながら、 TCP_CORKが設定されている場合であっても、このオプションを設定すると、送信待ちの出力を明示的に掃き出す (flush) ことになる。

以前のエントリーと繰り返しになってしまいますが、上記の引用のようにTCP_NODELAYを有効にすると 「データ量が少ない場合でも各セグメントは可能な限り早く送信される」 わけですね。そうすると、今回の実験のケースのような場合、細かいパケットが通常よりも大量に送信されてしまう可能性があります。そこで、それを防ぐためにせめてMSG_MOREを使ったほうが良いという指摘をうけたのだと思います。MSG_MOREが何なのかというman sendで以下のように解説されています。

% man send

MSG_MORE (Linux 2.4.4 以降)
  呼び出し元にさらに送るデータがあることを示す。このフラグは TCP ソケットとともに使用され、TCP_CORKソケットオプションと同じ効果が得られる (tcp(7) を参照)。TCP_CORK との違いは、このフラグを使うと呼び出し単位でこの機能を有効にできる点である。Linux  2.6 以降では、このフラグは UDP ソケットでもサポートされており、このフラグ付きで送信された全てのデータを一つのデータグラムにまとめて送信することを、カーネルに知らせる。まとめられたデータグラムは、このフラグを指定せずにこのシステムコールが実行された際に初めて送信される (udp(7) に記載されているソケットオプション UDP_CORK も参照)。

これだけではわからないので、TCP_CORKも引用しておきます。

% man tcp

TCP_CORK
  セットされると、partial フレームを送信しない。このオプションが解除されると、キューイングされたpartialフレームが送られる。これはsendfile(2)を呼ぶ前にヘッダを前置したり、スループットを最適化したい場合に便利である。現在の実装では、TCP_CORKで出力を抑えることができる時間の上限は200ミリ秒である。この上限に達すると、キューイングされたデータは自動的に送信される。Linux 2.5.71 以降においてのみ、このオプションをTCP_NODELAYと同時に用いることができる。移植性の必要なプログラムではこのオプションを用いるべきではない。

この解説の通り、部分的な小さいフレームは送信せずにキューイングしたかたまりを送ってくれるようになります。そして、このフラグはTCP_NODELAYと同時に用いることができます。上記で引用したTCP_NODELAYの解説によるとTCP_CORKによってTCP_DELAYは上書きされてしまい、TCP_MSGが優先されるようですね。しかし、送信待ちの出力を明示的にflushするとなっているが、これがどう振る舞いに影響するのかはもっと詳しく調査してみないと定かではないのでここでは述べないことにします。

MSG_MOREをThrift::Socketで使ってみようと思って、perlライブラリに入ってるSocketライブラリを見てみたのですが、まだMSG_MOREは対応していないみたいでした。おそらくDanga::Socketなどを使えばできそうかもしれませんが、Thrift::Socketを大々的に書きかえる作業は今はやりたくないので今回はあきらめました。

で結局どうしたらいいのか

ここまでの考察で、確信はできないけれどもTCP_NODELAYやMSG_MOREを使うことは場合によっては危うい感じもします。であれば、少しでもリスクになりうるTCP_NODELAYフラグは使わないほうがよいと僕は結論を出しました。というのもtokuhiromさんによって示された、Thrift::BufferedTransportを使ってreadをバッファリングする方法で十分に高速化できることを確認できたためです。

----

多重化してベンチをとる

「282QPSってむちゃくちゃ遅いと思う。てか、サーバサイドのロジックがここまで単純なら、多重化してベンチとるべき」とのご指摘をうけ、「うう、確かにそうだなあ。。」と思い、server(c++) + client(perl + Thrift::BufferedTransport)の構成でクライアントを多重化してベンチをとってみることにしました。

server側をマルチスレッドにする

今回もプログラムは以前のエントリー(Thriftが便利すぎる)で紹介した「足し算・引き算」サーバを使います。この構成ではサーバ側はC++で実装しています。そしてTSimpleServer.hというシングルスレッドのサーバを使っているため、このままの状態でクライアントを多重化してsendしてもシングルスレッドで処理されてしまいます。TSimpleServerで当方の環境で多重化したクライアントで使用したらサーバがアボートしました。

そこで、今回はマルチスレッドのサーバを作成することにします。したがってTSimpleServerではなく、TThreadPoolServerを用います。そのほかにもサーバ用ライブラリはいくつかありますが時間の関係もあり全ては未調査なので、このエントリーでは他のライブラリに関しては割愛させていただきます。以下に今回のベンチで使用したTThreadPoolServerを使ったサーバの実装を示します。

#include "TinyCalc.h"
#include <protocol/TBinaryProtocol.h>
#include <server/TThreadPoolServer.h>
#include <concurrency/ThreadManager.h>
#include <concurrency/PosixThreadFactory.h>
#include <transport/TServerSocket.h>
#include <transport/TBufferTransports.h>

using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::server;
using namespace apache::thrift::transport;
using namespace apache::thrift::concurrency;


using boost::shared_ptr;

class TinyCalcHandler : virtual public TinyCalcIf {
public:
  TinyCalcHandler() {
    // Your initialization goes here                                                                                                                                                
  }

  double sum(const double param1, const double param2) {
    return param1 + param2;
  }

  double subtract(const double param1, const double param2) {
    return param1 - param2;
  }
};

int main(int argc, char **argv) {
  int port = 9090;
  int workerNum = 50;
  shared_ptr<TinyCalcHandler> handler(new TinyCalcHandler());
  shared_ptr<TProcessor> processor(new TinyCalcProcessor(handler));
  shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
  shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
  shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
  shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(workerNum);
  shared_ptr<PosixThreadFactory> threadFactory =
    shared_ptr<PosixThreadFactory>(new PosixThreadFactory());
  threadManager->threadFactory(threadFactory);
  threadManager->start();
  TThreadPoolServer server(processor, serverTransport, transportFactory,
                           protocolFactory, threadManager);
  server.serve();

  return 0;
}

TSimpleServerとの大きな違いはスレッドのマネージャとしてThreadManagerを使うことです。上記の例のように使い方は簡単です。ThreadManager::newSimpleThreadManager(workerNum)この関数では、ThreadManagerを生成するときにworkerの数を指定します。今回は50にしましたが環境に合わせて引数を与えてください。

今回ベンチに使用したソースをUPしておきましたので、ご興味がある方は参照してみてください。コンパイルの仕方などは同封されているREADMEに記載しておきました。

TinyCalc.tar.gz

ベンチ方法

今回のベンチテストではサーバー1台、クライアント8台の構成で行いました。サーバ、クライアントすべて以下のスペックです。

cpu: AMD Opteron 2.4GHz * 4core
memory: 4GB

サーバについて

サーバのプログラムは上記のTinyCalc_server.cppを用い、ThreadManagerの引数に与えるworkerの数は50で行いました。

クライアントについて

クライアントはperlクライアントを用い、実装を以下に示します(TinyCalc.tar.gzに入っています)。localhostの部分は環境にあわせて修正してください。

1クライアントに5プロセスずつ同時に走らせ、クライアントサーバはすべてで8台あるので合計40プロセスが同時に1台のThriftサーバに問い合わせます。

#!/usr/bin/env perl

use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/gen-perl/";

use lib './gen-perl';
use Thrift;
use Thrift::BinaryProtocol;
use Thrift::BufferedTransport;
use Thrift::Socket;
use TinyCalc;

use constant TEST_NUM => 100000;

my $transport = Thrift::Socket->new('localhost', 9090);
my $client = TinyCalcClient->new( Thrift::BinaryProtocol->new(Thrift::BufferedTransport->new($transport)) );
$transport->open();

for(my $i = 0; $i < TEST_NUM; $i++){
    my $arg1 = int(rand(10000));
    my $arg2 = int(rand(10000));
    my $sum = $client->sum($arg1, $arg2);
    my $subst = $client->subtract($arg1, $arg2);
    print "$sum\t$subst\n";
}
$transport->close();

結果

上記の構成で5回計測したところ平均28649QPSとなりました。処理は簡単であるためサーバ側のCPUはまだまだ余裕だったので,クライアントが増えればまだあげられると思います。でも、そろそろネットワークがボトルネックになるような気もしました。

考察

僕が想定しているThriftのユースケースでは、主に分類器やクラスタリングなどに使おうと思っています。分類器などの場合は、分類器自体の計算量が高いのでボトルネックは分類器となる可能性が高いと思います。今回Thriftで簡単な処理をさせて約3万QPSが出ることが確認できたので、分類器などを使う場合、Thriftは十分なスピードかな、と思いました。分類器でThriftを使う場合は転送させるデータ量も意識しないといけないので、圧縮して送ったりしないといけないかな、とか考えたりしています。

まだThriftのチェックできていないライブラリも多くあるので、今後もよさそうな情報があったらエントリーを書きたいと思います。情報提供していただいた皆様ありがとうございましたー。当方、ネットワークの知識はあまりないのでとても勉強になりました。多謝多謝。


SIGIR 2009の論集が届いた

2009/09/28 23:23
shunya

今日帰宅したらACMから何やら届け物がきてて,中身をみたらなんとSIGIR 2009の論集でした.SIGIRというのは権威ある情報検索系の国際会議でとてもハイレベルな発表が集まります.僕は会員になっているのですが,ちゃんと論集を海外から送ってくれるなんて律儀ですなあ.

image:1:3962757362_ee1c575fdf.jpg

量が多いので全部目を通す自信も体力もないですが,ざーっと見た感じ興味ありそうなのを抜粋.

personalize系

  • 最近はやっぱpersonalize系がはやっているんですねえ.というか需要が高いのか.
Chen, Chun
Personalized Tag Recommendation Using Graph-based Ranking on Multi-type Interrelated Objects  (Page 540)
Chen, Homer
Personalized Music Emotion Recognition  (Page 748)
Chen, Liwei
Predicting User Interests from Contextual Information  (Page 363)

Clustering

  • Clusteringはジャンルを問わず興味があります
De Vries, Christopher M.
K-tree: Large Scale Document Clustering  (Page 718)

Social Network

  • SIGIRではソーシャルネットを使った手法はあまり見られなかったのですが,今回はいくつかあるようですね.
Gonçalves, Marcos
Detecting Spammers and Content Promoters in Online Video Social Networks  (Page 620)

Keyphrase Extraction

  • こうゆう知識抽出的な研究が大好き.軽く目を通しただけだからわからないけれども,Ranking SVMを使ってるみたい.
Hu, Yunhua
A Ranking Approach to Keyphrase Extraction  (Page 756)

日本人では藤井先生の発表が採択されたみたいです.すばらしい!

Fujii, Atsushi
Evaluating Effects of Machine Translation Accuracy on Cross-Lingual Patent Retrieval  (Page 674)

少しずつ目を通していきたいと思います.面白いのがあったらいつか紹介しますー.


Thriftのスピードが改善しました

2009/09/28 19:59
shunya

9/29 追記

tokuhiromさんにより,Thrift::BufferedTransportを使って高速化する方法が紹介されました.おそらく,現状ではperlのクライアントの高速化では一番シンプルで高速化できると思います.tokuhiromさん,情報共有していただきありがとうございます!

http://d.hatena.ne.jp/tokuhirom/20090928/1254155859

----

先週末はThriftのスピード問題にはまり、ガンダム戦記にはまり、ほとんど外に出られませんでした。前回のエントリー(Thriftのスピードについて)の続きとなります。

やっぱりperlのクライアントライブラリに問題がありそう?

多くの有識者の方にアドバイスをいただき感無量でございます。前回のエントリーでは、perlライブラリ、pythonライブラリでThriftが異常なほどに遅いんじゃないか?といった内容でございました。当方のバグではないかと、おそるおそる前回のエントリーをポストしたのですが、tokuhiromさんがこの現象に関して調査と考察の結果を示してくれました(ThriftのPerl Clientが遅すぎる件について)。

クライアントが Pure Perl で書かれており、かつ実装に適当さが感じられ、「速そうには、みえないな。。。」と感じました。
Facebook 内で実際に使用されているとおもわれる、PHP Client の方がつくりこまれている可能性があるので、こちらをためしてみると、また結果が違うかとおもいます

http://d.hatena.ne.jp/tokuhirom/20090928/1254093779より引用

perlのThriftライブラリを眺めてみたのですが、確かにあまり最適化されている感じではないかもしれないですね。僕はPHPは素人なので、PHP Clientのクライアントライブラリがどれほどよく作りこまれているのかはあまり判断できませんが、最初っからPHPでベンチをとればよかったなあとちょっと後悔。実は今もまだ試せていないので、今週末あたりにチャレンジしてみようと思います。

そして、同エントリーでJSONRPC over HTTP in pure perlな環境を構築していただき、やっぱり結果的にThriftのperlライブラリは遅いよねという調査結果をしてしてくれました。そして、なお同エントリーではThriftのperlライブラリを実行したときのstraceした結果までもを示してくれており、以下の表のような結果になり、毎回getpeernameをコールしているのはどうなのかといった可能性を示してくれました。tokuhiromさんありがとうございます。

syscall #/request
select 19
send 11
recvfrom 8
getpeername 12

解決策の検討

ここまでの調査でやっぱりperlライブラリに問題がありそうかなという目星がついてきました。で、今朝にmikioさんからtokyo tyrantでも開発中にSocketまわりで同じような問題があったよーと教えていただき、ThriftのperlライブラリのSocket周りを調査しました。

Nagleアルゴリズムが原因

以下のサイトに詳しく解説してあるので引用したいと思います。

Linuxにおけるソケット機能の向上

通常、デフォルトの設定におけるソケット通信ではNagleアルゴリズムというものが使われています。

TCPソケットを使用して通信する場合、やり取りされるデータはブロック単位に分割され、通信用のTCPペイロードに格納されます。TCPペイロードのサイズはいくつかの要因(パスに応じた最大パケット・サイズなど)によって決定されますが、通信が開始されるまでこれらの要因を特定することはできません。最高の性能を実現するには、それぞれのパケットにできるだけ多くの使用可能データを格納することが必要です。ペイロード内に十分なデータがない場合(ペイロードのサイズが最大セグメント・サイズ(MSS)になります)、TCPはNagleアルゴリズムにより、複数の小さなバッファーを自動的に連結して1つのセグメントを作成します。これにより、アプリケーションの処理効率を上げ、小さなパケットの送信数を最小限に抑えてネットワーク全体の混雑を緩和します。

http://www.ibm.com/developerworks/jp/linux/library/l-hisock/index.html より引用

ちょっと難しい説明かもしれませんね。以下の書籍にもうちょっと詳しく書いてあったので引用します。

UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI

W.Richard Stevens

image:1:213B9PVJD1L._SL160_.jpg
Nagleアルゴリズムの目的は、WAN上の小さなパケットの数を減らすことにある。このアルゴリズムでは、あるコネクション上で未解決(outstanding)データ(送信済みで応答を待って待機中のデータ)がある場合、それらが承認されるまで、そのコネクション上には小さなパケットを送信しないことになっている。ここで``小さな''パケットとは、MSSより小さなすべてのパケットを指す。TCPは可能な限り最大長のパケットを送信しようとし、Nagleアルゴリズムの目的は、複数の小さなパケットが同時に未解決になることを防止することにある。

UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI P.196 より引用

つまりわかりやすく言うと、膨大なデータをやり取りする場合は細切れにパケットを送信するよりも、最大長になるように待ってから送信したほうが効率がいいし、速度があがるわけですね。

wikipediaにも解説があったので興味がある方は参照してみてください。

http://en.wikipedia.org/wiki/Nagle%27s_algorithm

解決策

で、先ほどの解説ページには以下のように解決策が示されています。

解決策

まず覚えておかなければならないのは、Nagleアルゴリズムは有効であるということです。TCPパケット・セグメントの容量いっぱいにデータを格納しようとするため、パケットの送信までにはどうしても待ち時間が発生してしまいますが、ネットワーク上でのパケット送信数を最小限に抑えるという利点があり、結果的にネットワーク全体の混雑を緩和することができます。

しかし、この送信待ち時間を最小限にしたい場合には、ソケットAPIを使用するのが有効です。Nagleアルゴリズムを無効にするには、リスト1のようにソケット・オプションのTCP_NODELAYを設定します。

http://www.ibm.com/developerworks/jp/linux/library/l-hisock/index.html より引用

今回のThriftのユースケースではファイル転送のようにデータを連続的に送信するわけではなくて、細かい計算の要求を送っては結果を返すという処理になっているので、Nagleアルゴリズムの待ち時間は無駄な時間になってしまうんですね。計算要求をするたびに少しwaitがかかってしまう感じになってしまいます。上記の引用先にも書いてあるように、TCP_NODELAYというオプションを使うとNagleアルゴリズムが使われなくなります。そこで、ThritfのperlクライアントライブラリでもTCP_NODELAYを使えばスピードが上がるのではないかという、仮説が立てられます。

ThriftのperlライブラリでNagleアルゴリズムを使わないようにするには

修正するのは、

Thrift::Socket

です。

Thriftのパッケージに含まれている、以下のファイルを修正します。

thrift/lib/perl/lib/Thrift/Socket.pm

Thrift::Socketの中にopen()というコネクションを確立させる関数があります。この関数にNagleアルゴリズムを使わないようにするために以下の一行を追加します。123行目あたりにあたりにある$self->{handle} = new IO::Select( $sock ) の上に追加します。

$sock->setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1);

修正したdiff

123c123
<     $sock->setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1);
---
> 

これでNagleアルゴリズムを使わなくなります.

結果

修正する前の結果

前回のエントリーにも載せましたが、修正する前はThriftよりRESTのほうが約8倍速い結果となりました。

image:2:1253876512-ThriftSpeedTestResult.png

Nagleアルゴリズムをオフにした結果

image:3:1254134345-ThriftSpeedTestResult2.png

なんと、速度が逆転しました。逆にThriftがRESTよりも約2.78倍速い結果となり、QPSは282とかなり良い結果となりました!

まとめ

Thrift::SocketでNagleアルゴリズムを使わないようにすることによってThriftのperlクライアントライブラリの速度が飛躍的に向上しました。このスピードであれば、サービスのバックエンドなんかでは十分に使えるパフォーマンスだと思います。他の言語のクライアントでは試していませんが、おそらくPythonでも同じような設定にすることによってパフォーマンスが向上することと思います。phpに関してはちょっとわかりませんが。。。

また、tokuhiromさんの調査によりそのほかにもThriftのperlライブラリにはいくらか無駄があるので、まだまだハックする余地はありそうです。282QPSという値だけでも十分なのに、XSで書いてもっと早くなったら、ちょっとこれはすごいですね。

nokunoさんがコメントで教えてくださったように、サーバーサイドでもチューニングの余地がまだまだありそうです。今後はThriftのサーバーごとのパフォーマンスをチェックしてみたいと思います。

一応念を押しておきますが,ユースケースによってはThriftでもNagleアルゴリズムを使ったほうが高速な場合はあると思います.ですので,今回の修正はユースケースに合わせて使ってみることをお勧めします.

ご協力いただいた皆様ありがとうございましたー。

Thirftって 「すごく、いい」 です