2009/06/14


先日書いた、「コマンドラインからGoogle Readerが使えるPeepを試した」でLinuxでは快適になった。ただWindowsでは動かない。まずWindowsのpython2.6にはcursesのバイナリモジュールが含まれていなかった。これは「Python2.6にはcursesのバイナリが含まれていないので作る」で解決したのだけど、元になっているpdcursesがWindows上ではまともに動かなくて、例えばマルチバイト文字を含んだ複数行をscrl(スクロール)すると、正しくマルチバイト文字の幅を取れていないのか下段の行が上段の行にゴミとなって重なったりした。
Peep自身を触っても良かったのだが修正非量も多い。常に残念だったのでCで書く事にした。APIの呼び出しまわりはPeepを参考にさせて頂き、libxml2やcurl、pdcurses(linuxではncurses)で書き直す事にした。ただWindows上でcursesの動作が変なのは変わらない話なので、scrlを使わない事にした。どうやったかというとスクロールの度にclear()を呼んで全行書き直し。試してみた所、それ程操作感も悪く無かった。また色属性についてもACS_REVERSEを使うと幅計算が間違っているのか1行の幅で反転してくれなかったので、A_BOLDを使う事にした。現状、WindowsでもLinuxでも動作するようになっているけど、C++のstlをふんだんに使っててメモリ効率を何も考えてないソースになっています。
mattn's cpeep at master - GitHub

terminal front-end for Google Reader writen in C

http://github.com/mattn/cpeep/tree/master
cpeep1
cpeep2
まだまだ、全く作りかけなので閲覧しか出来ません。
j,kスクロールと、oで閲覧、qで閉じる、vでブラウザ(現状Windowsは通常使うブラウザ、それ意外はfirefox限定)です。
ちなみにPeepはHTMLをテキストに変換するのにw3mを内部で呼び出していたけど、cpeepでは自前でウンチャラカンチャラやってます。
まだまだ、これからです。

ちなみに今気付いたのですが、PeepのAuthorさんからPeepのcommit bitを付与して頂いている様です。ありがとうございます。何か協力出来る事があればcommitさせて頂きます。

追記
なんかの間違いだった様です。すみません。
Posted at by



2009/06/13


追記
パッチがいるのを忘れていました。
一番下にあります。

バッチファイル。実行すると直接バイナリ配置するので気をつけて。バッチの先頭にあるパスを環境に合わせ修正して下さい。
pythonのソースはオフィシャルから「Python-2.6.2.tar.bz2」を、pdcursesもオフィシャルから「pdcurs34.zip」をダウンロードし"C:¥TEMP¥"に展開しておきます。
@echo off
setlocal

set PYTHON26=c:\Python26

set PYSOURCE=c:\temp\Python-2.6.2
set PDSOURCE=c:\temp\pdcurses

if "%1" == "msvc" goto msvc
if "%1" == "gcc" goto gcc
goto usage

:msvc
set FLAGS=/nologo /LD -DHAVE_CURSES_RESIZE_TERM -DNCURSES_MOUSE_VERSION=2
echo building _curses.pyd
cl %FLAGS% -I%PYTHON26%\include -I%PDSOURCE% %PYSOURCE%\Modules\_cursesmodule.c /link %PYTHON26%\libs\python26.lib %PDSOURCE%\win32\pdcurses.lib %PDSOURCE%\win32\panel.lib /out:%PYTHON26%\lib\_curses.pyd
echo building _curses_panel.pyd
cl %FLAGS% -I%PYTHON26%\include -I%PDSOURCE% %PYSOURCE%\Modules\_curses_panel.c /link %PYTHON26%\libs\python26.lib %PDSOURCE%\win32\pdcurses.lib %PDSOURCE%\win32\panel.lib /out:%PYTHON26%\lib\curses\_curses_panel.pyd
goto end

:gcc
set FLAGS=-DHAVE_CURSES_RESIZE_TERM -DNCURSES_MOUSE_VERSION=2 -Wl,--enable-auto-import -Wl,--export-all -s -shared
echo building _curses.pyd
gcc %FLAGS% -I%PYTHON26%\include -I%PDSOURCE% %PYSOURCE%\Modules\_cursesmodule.c %PYTHON26%\libs\libpython26.a %PDSOURCE%\win32\pdcurses.a %PDSOURCE%\win32\panel.a -o %PYTHON26%\lib\curses\_curses.pyd
echo building _curses_panel.pyd
gcc %FLAGS% -I%PYTHON26%\include -I%PDSOURCE% %PYSOURCE%\Modules\_curses_panel.c %PYTHON26%\libs\libpython26.a %PDSOURCE%\win32\pdcurses.a %PDSOURCE%\win32\panel.a -o %PYTHON26%\lib\curses\_curses_panel.pyd
goto end

:usage
echo pycurses_build.bat [msvc or gcc]

:end
endlocal
以下ソースにあてるパッチです。Windows上のsetuptermは必ずエラーで返すコードになっているので呼ばないようにしています。
--- Modules/_cursesmodule.c.orig    2009-06-13 01:30:02.000000000 +0900
+++ Modules/_cursesmodule.c 2009-06-13 01:30:23.000000000 +0900
@@ -2036,6 +2036,7 @@
        }
    }
 
+#ifndef _WIN32
    if (setupterm(termstr,fd,&err) == ERR) {
        char* s = "setupterm: unknown error";
        
@@ -2048,6 +2049,7 @@
        PyErr_SetString(PyCursesError,s);
        return NULL;
    }
+#endif
 
    initialised_setupterm = TRUE;
 
Posted at by



2009/06/11


Tagtterをリリースして、初期は「CPU Quota」連発してしまい見苦しい状態でした。ごめんなさい。
さて、一時はどうなるかと思いましたが現在は「CPU Quota」も表示される事無く動いています。
今日はこの状況を乗り切る際に行った「Google App Engine」のパフォーマンスチューニングtipsを3つほどご紹介。

webapp.WSGIApplicationのdebugフラグはFalseにすべし

いきなり当たり前で申し訳ないですが、実はこのフラグがTrueかFalseかというだけで「CPU Quota」が出る頻度が極度に異なります。
実際に同じ症状の方もいらっしゃる様なので、おそらく間違いありません。
また公開する様なシステムでは、パスワード等も管理される事になるかと思いますが、debug=Trueだとスタックトレースに変数の値が表示されてしまい無茶苦茶危険です。気を付けましょう。
なお、Tagtterではユーザのパスワードは一切保存していません。

時間の掛かる既知な処理はキャッシュする

これはGoogle App Engineだけに言える話ではないですが、Tagtterではユーザの存在確認の為にtwitter.comへアクセスしています。
但し、タグが追加される度、voteされる度にこれを行ってしまうとtwitter.comにも負荷を掛ける事になります。
そこでTagtterではTagtter内に存在するユーザに対してはtwitter.comへのアクセスを行わないようになっています。
確かにGoogle App Engineの処理能力は良いのですが、いかんせん制限が厳しかったりします。余談になりますが現状Tagtterのデータ全件をXMLエクスポートする処理は「CPU Quota」で動きません。現在はデータの種別毎にバックアップしています。

Templateにはクラスオブジェクトは渡さない

Django Templateには、db.Modelのインスタンスを渡せて非常に便利なのですが、このDBインスタンスをTemplateの中から扱うと非常に遅くなります。
またdb.Modelに持ったgeneratorをTemplateから使うと更にパフォーマンスが落ちます。
例えばTagtterではユーザに対して付けられたタグをManyToManyで管理しており、モデルの一部を抜粋すると class TagtterTag(db.Model):
  name = db.StringProperty(required=True)

  ... snip ...

class TagtterMembership(db.Model):
  user = db.ReferenceProperty(TagtterUser)
  tag = db.ReferenceProperty(TagtterTag)

  ... ship ...

class TagtterUser(db.Model):
  name = db.StringProperty(required=True)

  ... snip ...

  def tags(self):
    return (x.tag for x in self.tagttermembership_set)

この様なコードになるのですが、user.tagsをTemplate側から呼び出すとかなりパフォーマンスダウンになります。また、Google App EngineではCPU占有度により「CPU Quota」させる仕組みがあるのですが、実はこれはTemplate内の呼び出しにも適応される為 application → template → model procedure という処理を行うと、1処理(1値参照)あたりのCPU使用回数が増える事になります。
つまり上記Tagtterのtagsページでは全てのタグが表示されますがこれを     template_values = {
      'tags' : TagtterTag.all(),
    }
この様にしてしまうとTemplate側からModelのメソッドを呼び出す事になり、現状のデータ件数だと100%エラーが発生してしまいます。
Google App EngineではCPU占有状態が5秒程続くとエラーになる様で、このブリッジ状態だけでもパフォーマンスダウンに繋がるって事になります。
これを回避する為には、applicationにてTemplateで使う値を全て保持してしまう事が最も効果的でした。
つまり全てdictとして渡してあげるのです。変にTemplate側で「タグの数が0でない物を表示」とするのではなく     all_tags = []
    for tag in TagtterTag.all():
      if tag.size():
        size = tag.size()
        all_tags.append({
          'name' : tag.name,
          'title' : tag.title,
          'size' : size,
          'fontsize' : font_size(size),
        })
    ... ship ...

    template_values = {
      ... ship ...

      'tags' : all_tags,

      ... ship ...
    }
この様にTemplate側からdb.Modelメソッドを呼び出させないようにするのです。 これで、現状エラーも出なくなりました。要するに極力db.Modelインスタンスにアクセスしない様にするってのが味噌ですね。
こんな品祖貧祖なパフォーマンスチューニングですが、皆さんの何かしらのお役に立てば幸いです。

他にもパフォーマンスチューニングの方法は幾らでもあるかと思います。Tagtterで言えばクライアント側の処理は殆どパフォーマンスチューニングされていないのが現状です。
不定期ですが時間を見付けてやって行きたいと思います。

何かご要望などあれば、Feature Requestまで。
Posted at by