トップ 追記

enbug diary


2009-12-24

_ buildoutを高速化する5つの方法

Zope/Ploneアドベントカレンダー 2009 のトリ--に選ばれたので、buildout関連のネタを少々。

みなさんご存知の通り、 buildout は、本来Zope3のために作られた構築ツールですが、 Zope2の完全egg化が2.12で果たされたので、 PloneERP5 等のZopeベースのプロジェクトでも積極的に採用されたり、 Pylons、Django、Repoze等、数多くの有名プロジェクトでも活用されたり、 今最もホットなPythonプロジェクトの一つと言えるでしょう。

さて、前振りはここまでとして、 buildoutを日常的に使うようになってくると、 もっと速く快適に使いたいという欲求がたくさん出てくるはずです。 そこで、buildout中毒になっている人がもっと気持ち良くなれる方法をいくつか紹介します。

まず、buildoutを使うと、簡単にローカル環境が構築できるので、複数バージョンを保持する機会が増えるでしょう。 普通にやると、似たようなbuildoutを使っていても、 eggは別々にダウンロードされて、インストールされます。 でもこれは大抵無駄ですよね。

そう感じたら、自分のホームディレクトリに .buildout/default.cfg というファイルを作り、 その中に、 eggs-directory を定義します。

$ test -d ~/.buildout || mkdir .buildut
$ test -d ~/eggs || mkdir ~/eggs
$ cat <<EOF >~/.buildout/default.cfg
[buildout]
eggs-directory = $HOME/eggs
EOF

こうすれば、デフォルトでeggの置き場所がホームの下のeggsというところになって、 すでにあるeggをまたインストールし直したりしなくなります。

同じような話で、複数の環境で同じようなbuildoutを使っている場合、わざわざ PyPI から取ってくるのは無駄ですよね。 負荷が高くて遅いですし、たまに落ちていることもありますし。

そういう時には、 collective.eggproxy を使いましょう。 このツールは、PyPIのプロキシーとして動作し、 eggとかindex情報をキャッシュしてくれます。 使い方はとても簡単。

$ easy_install collective.eggproxy
$ mkdir /tmp/eggs
$ eggproxy_run

デフォルトで、localhost:8888を使用しますので、 あとは buildout.cfgindex を指定すれば、大丈夫。

[buildout]
index = http://localhost:8888/

さて、ますますbuildout中毒になってくると、いかにしてbuildoutの設定ファイルを共有するかが問題になってきます。 buildoutは extends を使って、設定ファイルを継承させることができるので、当然こんな感じのことをしたくなります。

[buildout]
extends = http://www.example.com/buildout/base.cfg

しかし、このままだと、buildoutを走らせる度、毎回アクセスしに行ってしまうので、遅いし、落ちていると機能しなくなります。

そこで簡単な方法は、extends-cache を使うことです。 このパラメータを使うと、extends の対象がキャッシュされ、毎回ダウンロードしなくて済むようになります。

[buildout]
extends = http://www.example.com/buildout/base.cfg
extends-cache = cache

しかし、それでも頻繁にbuildoutを走らせるとなると、やっぱり遅いって感じることはあります。 そういうbuildout依存症の人は、-N を付けて走らせましょう。

$ bin/buildout -N

こうすれば、新しいバージョンのチェックを行わなくなるので、完全にアップデートする必要がない場合、圧倒的に速くなります。 このオプションをほとんどいつも使うようであれば、 ~/.buildout/default.cfg に書いてしまいましょう。

[buildout]
newest = false

ただし、本当に新しくしたい時には、このオプションが設定に入っていると、明示的に -n を付ける必要が出てきますので、注意しましょう。

$ bin/buildout -n

後もう一つだけ。 buildoutの動作の高速性はともかく、 設定ファイルをいじるのを楽にしたい人、 そんな人は mr.developer を見てみましょう。 きっと役に立ちます。

以上。

お名前 : コメント :


2009-11-21

_ 第5回Zope/Plone開発勉強会の報告

現実逃避がてらに、(第5回)Zope/Plone開発勉強会に行ってきました。 以下は報告です。

今回は、Python 2.6で追加された メソッド・キャッシュによる最適化Zope でも享受したいということで、作業しました。

このバグ に記述されているように、既存の拡張が壊れる可能性があるため、 このオプションはデフォルトでは拡張モジュール内では有効になりません(コアに入っているタイプでは有効になってます)。 それゆえ、手作業で確認しながら修正していくしかありません。

ターゲットにしたのは、

の4つのパッケージです。 これでZope2で使うタイプとしては、大体カバーしていると思います。

成果としては、一応動くようにして、本家にパッチを投げました。

対応させて、テストを完了させるだけで精一杯だったので、これでどれだけ高速化されるのかは確認してません。

お名前 : コメント :


2009-11-01

_ OSC2009 Tokyo/Fallで発表しました

昨日「Web開発者に贈る昨今のPython事情」の発表を終えました。 たくさんの方に来ていただき、ありがとうございました。 発表資料を公開しましたので、ご自由にご利用ください。

終了後は、何人かの関係者(?)を無理やり付き合わせて、呑み会に突入してました。 付き合ってくださってありがとうございました。 大変楽しかったです。

補足:面倒くさいだろうと思うので、いくつか関連リンクを貼り付けておきます。

お名前 : コメント :


2009-09-30

_ OSC2009 Tokyo/Fallで発表します

10月31日(土曜日)の15時15分から16時まで、Web開発者に贈る昨今のPython事情 と題して、 オープンソースカンファレンス2009 Tokyo/Fall にて発表させていただけることになりました。 今回は、ビジネス寄りの話題やコア開発者しかわからないようなネタは避けて、あんまり詳しくない人でもわかってもらえるような講演にするつもりです。 都合の付く人は是非来てください。

お名前 : コメント :


2009-09-23

_ 連想配列に使うハッシュ関数

unladen-swallowのissue を見て、ちょっと気になったので、昨今のハッシュ関数の事情を調べていたのですが、 一言でいうと、難しい、です。

Pythonのハッシュ関数は、今でも FNV っぽいアルゴリズムを用いています。 ただし、実際には独立して開発されていて、たまたま似ているというだけだそうです。

で、 Hash functions: An empirical comparison なんかを見ると、 Murmur2 が汎用的には奨励されていて、 FNVよりも大体速くて、場合によっては衝突も少ないという結果が出ているみたいです。

では、なぜPythonがあいもかわらずFNVっぽいのかというと、 Python 3000のハッシュアルゴリズムの議論 でも見られるように、「ランダムなハッシュ関数は望ましくない(かもしれない)」というのが根拠らしいです。 つまり、似たようなキーがあったとき、ちょっとずつハッシュ値がずれてくれる方が、特にdictが小さいときに、衝突が減少するので、似たようなキーがランダムに散らばるよりも、性能が高いということです。

この辺の議論はなかなか複雑で、ハッシュ関数の特性に求められることとして、

  • 計算が高速であること。
  • 値域が広いこと(例えば、64-bit空間に広がる等)。
  • 衝突が少ないこと。

等など、さまざまな要素があげられますが、連想配列に適用する場合、さらに、

  • 丸められた値域の中で衝突が少ないこと(連想配列のサイズは通常ハッシュ関数の値空間よりも小さいから)。
  • 丸められた値域の中で一様に広がること(メモリ効率のため)。

等が付け加わります。 逆に、連想配列では、暗号等で求められるような、同じハッシュ値を持つキーが作りにくいという要求はあまり高くありません。 もちろん、DoS問題とかもあるんですが、ここではその問題は無視することにします。

Murmurの問題は、乱数性能が高くて、下位1ビットを変えるだけで、全然違うハッシュ値になってしまうことです。 そうすると、a、b、cみたいな似たようなキーばかりやってくると、 連想配列中で衝突する可能性が高くなります。

しかし、特にdictが大きくなってくると、丸めて衝突する確率が下がるので、問題はむしろクラスタリングが発生することです。 クラスタリングとは、連想配列に含まれる要素が局所的に集まってしまって、衝突が増大し、かつ、使われないバケットが増えてしまう状態です。 これを避けるには、元のハッシュ値がうまく散らばってくれないといけません。

ですから、この種の問題は常に使い方や目的に依存していて、 何を重視し、何を軽視するかというバランスの取り方の問題になります。 CPythonはdict使いまくりの処理系であるため、 小さいdictが大量に発生しやすく、そこに最適化しています。

ですが、だからといって、本当に現状のままが最善なのか、簡単にはわかりません。 Murmurでランダムな値になって発生する衝突率の増大が、 Murmurの高速動作と比べてどうなのか、 dictが大きくなった時の衝突率の増大によって、 その利点が帳消になっていないのか、 こういうことをちゃんと調べてみないとわからないわけです。

自分で調べるのは面倒くさかったので、 とりあえず他がどうしているのか知りたくて、 Rubyを覗いてみることにしました。

最近のRubyはすでにMurmurを使ってます。 これは レポジトリを眺めて、すぐにわかりました。 ですが、ここからはわからないことだらけです。 最初のコミット を見ても、そうしたとしか書いていなくて、どうしてそうしたかったのかが書かれていません。 成瀬さんが提案されている にもかかわらず、古い方のマジックナンバーが使われていたり、 その方がよいからそうしているのか、ただ変更していないだけなのか、 最近Rubyの開発を全く追いかけていない私にはよくわかりませんでした。

なので、RubyからわかったことはMurmurを使っている、そのことだけでした。 Pythonでもそれほど定量的な解析がなされているわけでもなさそうだったので、 五十歩百歩なんですが、 せっかく覗いたのに情報が得られなかったのはちょっと残念です。

もっとも、RubyとPythonではかなり事情が違うので、 仮にわかったとしても、そのままPythonに当てはまるわけでもありません。 RubyのArrayは(自分の記憶では)chainingを使っているはずですし、 Pythonのdictはopenですから、衝突のコストや分布に求められる特性も自ずと違うものになるでしょう。 また、Pythonは32-bitのハッシュ値をopen arrayに押し込んで、 文字列比較をできるだけサボろうとしていますので、 元のハッシュ値が32-bit空間でどれだけうまく散らばっているかが、 かなり重要です(Rubyがここんところをどうしていたかは全然覚えてません)。

自分で少し実験してみてもいいんですが、 最初やりかったことから、またかなり脱線してしまっているので、 誰かがやってくれることを期待して、 止めておきたいと思います。

お名前 : コメント :

本日のツッコミ(全2件) [ツッコミを入れる]

_ なかだ [Murmur 2.0 2009-09-19に更新しました。rubyでもハッシュ値を保存しています。]

_ n [ReiserFSのハッシュアルゴリズムであるr5, rupasov, teaってどうなんでしょうかね。]


2003|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|06|07|08|09|11|12|
トップ 追記