Skip to main content

ログ取得ツール

Category: プログラミング

Pythonのネイティブコード呼び出しのコスト

Pythonでネイティブコードを簡単に呼ぶ方法として、ctypesとboost.pythonがある。ctypesは最近はPython本体に含まれるし、boost.pythonもBoost本体に含まれているようだ。この2つは、swigや自力でインタフェースを書く場合と比べてだいぶ楽に呼び出せる。

で、どのくらいコストが違うのか、試してみようと。ネイティブコードを呼んだ先では大差ないと思うけど、ネイティブコード呼び出しにかかるコストによっては、インタフェースの粒度を考えないといけなくなる。

そこで、ためしに

  • 何もしない
  • math.sinを呼ぶ
  • cmath.sinを呼ぶ
  • boost.python経由でstd::sinを呼ぶ
  • ctypesでlibmのsinを呼ぶ

という呼び出しの速度を測ってみた。cmathは複素数に対応した数学関数のパッケージです。sinが複素数を返すことはない…と思いますけど、mathとcmathの比較のために一応。200万回の呼び出して時間を測定して平均した。測定環境がVista(32bit)環境のVMWareの中のFedora8(x68_64)環境、というのがややこしい。こういう環境は測定が正確じゃないかもしれないですね。仮想環境のgettimeofdayはあんまり信用できないような気がする。vmware-toolsを入れて時刻をホストと同期はさせてますが。

日付と時刻を入れたいとき

ブログに情報を追記するときに、エントリの更新時刻は変えたくないけど、いつ追記したのかはわかるようにしたい、ということがある。

そういうときは「(追記)2008/02/10(日) 22:53」みたいな文字列を入れるといい。しかし使っているWindows Live Writerでそれを入れるにはどうしたらいいのか。プラグインをちょっと見たけどなさそうだし、途中でクリップボードに日付と時刻を入れるちょっとしたスクリプトを書けばいいじゃないかと思った。

しかし、調べていくとWindowsの標準的なスクリプト環境であるWSHはクリップボードにアクセスできないらしい。これは恥だと思わなければなりません。WSHはVBScriptとJScriptの2種類の言語が使えるらしいが、どちらも直接クリップボードにアクセスできない。しかしなぜか裏口があるらしく、(IE7/Vistaでも使えるかどうかは疑問だが)IEにクリップボードを読ませるという方法があるようだ。できるかもしれないけど、なんかこれは違うと思う。

LI情報とGPS

PHSにはLI情報というのがあって、捕まえたアンテナの強度の情報から三角測量のやり方で現在位置を求めることができる。302でLocationヘッダをつけて特定のURLに転送すれば、座標を返してくれるというインタフェースが用意されているので、簡単なCGIを動かせるWebサーバの環境さえあれば、誰でもこの情報を得ることができる。ちなみにこのときPHSのユーザに確認のダイアログを出すことで、プライバシの確保を担保しています(ので、安心して使える)。 そして、最近私はデジカメ写真に座標をつけているのだけど、GPSのデバイスを持ち歩くのは面倒くさいし、買ってみたGPSデバイスは精度も使い勝手もいまいち。ざっくりしたデータでいいので自動でつけて、細部は手で修正、としたいなぁと思った。この情報にLI情報が使えまいか。 というわけで作ってみたのがこれ。最初はどっかに転がってると思ってぐぐってみたんだけど、ありそうでなかった。PHS使ってる奴少ないからなー。 li2gps-0.0.1.tar.gz 思わずmod_pythonのPSPハンドラなんていうマイナーなものを使ってしまった。mod_pythonの(.htaccessの)設定をして、ディレクトリにApacheが書き込めるようにして、そこ(http://server.name/path/to/li2gps.psp)にPHSでアクセスすると、LI情報の送信の有無を聞かれますので送信するように答えると、gpxファイルが生成されます。日付ごとに別ファイル。PerlとかPHPで書けばもっと楽だったかもしれないけど、XMLを触るので、慣れているPythonを使いたかったのです。gpxよりNMEAとかにしたほうが楽だったかもしれない。 li2gps-0.0.1は複数のユーザがアクセスすることは想定してませんが、やったらそれなりにおもしろいのではないかな、とも思いますね。 gpxファイルという形式はそのままdigikam+kipi-pluginsで食うことができますし、gpsbabelを使えば他のフォーマットにも変換できます。外出中にPHSからli2gps.pspにアクセスしまくっておき、結果をkmlなんかにしてgoogle earthに食わせると、まるでGPSを持ち歩いたかのような結果が得られるはずです。 もともとのLI情報はGPSほどの精度はないみたいですが、ざっくりした位置は検出できてる感じですね。あと、測地系の違いで少し混乱しましたが、誤差がそこそこあるやり方でTOKYOからWGS84への変換をしています。地図サイトによってTOKYOだったりWGS84だったりして、ずれる。LI情報は見たところ、TOKYOだと思いますね。google mapの携帯版って、どう座標を与えればいいのだろうかと。 出かけて、写真を撮ろうというときにPHSで設置したページにアクセスしておき、位置と時刻を登録、写真を撮る。家に帰ってSDカードから写真を取り込んで、Webサーバにあるgpxファイルを食わせる。これでいいような気もするよね。ちょっと面倒かもしれないな。 まー、使い勝手に関してはもっと工夫の余地がありますね。gpxファイルとLI情報の使い方のサンプルみたいなものとして見ていただければいいかなぁ。 厳密にやるなら、カメラの時計とサーバの時計が合っていない場合の処置も必要だと思いますね。やるとしたら、サーバ標準時の時刻をJavaScriptかなんかで表示するようにして、それをデジカメで撮ってもらう。そしてその写真をサーバにアップして、表示されている時刻をOCRか人力で得て、EXIF情報にある時刻との差が時差なので、それを元に補正するとか。そこまでやる気は私にはないです(笑)

APNGはけっこう簡単な構造だった

Firefox3から入るらしいAPNG(Animated PNG)。MNGみたいに複雑すぎず、アニメーションGIFと同じくらいの機能が入っており、既存のPNGと互換性を保つ。互換性というのは、既存のPNG対応アプリで何も変更することなしに普通に特定のフレームが画像として見えているということ。これは大きい。 MNGは機能を詰め込みすぎたせいでもう死んでいる。APNGはモメたようでこの後もすったもんだがあるかもしれないけど、Firefoxが強引に入れてしまうようなので、デファクトスタンダードになるかもしれない。フルカラーでアルファブレンディングもできる簡易アニメーション、というのはブラウザさえ普及すれば需要があると思います。 というわけで、APNG及びPNGについてちょっと調べつつサンプルプログラムを書いてみた。まずPNGはシグニチャがあり、その後にチャンクの形式でずらずら続く。圧縮は全部zlibのdeflate。圧縮されるチャンクの種類は少なく、IDAT(画像データ)とzTXt(圧縮テキスト)、iTXt(言語情報を入れられるテキスト情報)くらい。IDATを開くとrawなビットマップなデータが入っている。非常に単純で特別な技術も使われておらず、実装しやすいという印象。まあ難しそうなのはinterlaceのコードと、あとはアルファブレンディングみたいな画像関係の技術が入るところくらいで、それも特に疑問なく実装できそうな気がする。 非圧縮PNGの形式を用意してgzip(zlib)で圧縮しただけ、でも良かったんじゃないのかな、という感想も持ってしまうな。けっこうすごいことしてんのかなと思ってたんだけど、こんなもんだったのか。 で、APNGはチャンク「acTL」「fcTL」「fdAT」の3つを追加する拡張。普通のPNG対応(APNG非対応)アプリはacTLやfcTL、fdATなんて知らないから無視して、通常のIDAT等を使うので問題ない。これはコンテナのタイプIDに4文字テキストを使ったPNG作者のセンスが勝因だろう。テキストのほうが見た目が分かりやすく、4文字固定なら最適化で32bit intとして見る実装も可能だしね。大文字小文字に意味を持たせるというやり方も分かりやすくていい。 APNGで拡張した内容は、「acTL」はアニメーション全体の情報(フレーム数やリピート回数)、「fcTL」はフレームの情報(ディレイ時間など)、「fdAT」はフレームのデータが入る。フレーム番号の扱いが微妙な感じを受ける。fcTLとfdATにはフレーム番号がついているが、別に対応するわけではなく連番。複数のfdATが1枚のフレームを構成するようになっていて、fdAT列の区切りとしてフレームごとのfcTLがあるという感じ。1枚のフレームを構成する複数のfdATそれぞれに別個にフレーム番号を振る必要があり、その番号がfcTLも含めた連番になっている。 だからfcTLのフレーム番号を見ても何枚めのフレームなのかは判別できない。そしてacTLに入っているフレーム数というのはfcTLの個数なので、対応をつけるのがアレですな、という感じ。 よく見ると(よく見なくても分かるけど)、fdATからフレーム番号だけ削除してそのままIDATに変更すれば普通のPNGを抜き出すことができる(CRC32を計算しなおす必要はあるが、圧縮し直す必要はない)。そんな簡単にできそうなapng2pngのコンバータ。 まあでもこんなに簡単な構造ならPure-Pythonで書こうよ、という印象なので、試しに書いてみた。APNG対応のアプリもまだ少ないことだし、サンプルとして見てもらえれば幸いです。 pngtest.pyもしくは、

XPSはどうなの?

XPS(XML Paper Specification)(microsoft.com)というのがあるらしい。MS製PDFみたいなものだ。紙を表現するXMLということだが、ビューワもWindows用にしか存在せず(.NETのなんたらを入れる必要があるとかないとか??)、孤高のファイルフォーマットという感じがする。最初はプリンタ用の言語(PSの代わり?)として作られたらしい。紙をXMLで表現するなど、なかなかよろしい話ではないか。だが仕様のダウンロードにLicense Agreementが必要なので仕様は落としてない。その代わり、サンプルにいくつかファイルをダウンロードしてみた。しょせんXMLだし、当分はこれで充分だろう。 というわけで仕様はともかく、MS配布のXPSファイルの中身を見ると、zipで束ねられた複数のXMLファイルだ。データとしてjpegやpng、内容は良く分からないが「odttf」という拡張子のファイルも入っていた(フォントだろうか?)。こういう構造はODFと同じようなもので、流行りかな。かなり効率は悪いと思うんだけど、何も考えなくても圧縮されるし解析がしやすいからオープンな規格にしようとするとこうなってしまう。XMLを見ているといろいろ冗長で、単純なものを扱うデータがどんどん複雑に表現されていく。入力と出力を共通化するだけのためにここまでやる必要があったのだろうか? YAMLとかJSONが出現した理由が分かるよね。 印象としては、MSはUTF-16のテキストファイルが好きですね、というのと、いろいろな流儀のXMLファイルが混合しているので生成するのはどうやってるのかが疑問、という感じ。 まず最初のUTF-16というのは、US-ASCIIしか使ってないファイルがUTF-16で書かれていたりする。「<?xml」宣言でエンコーディングを指定しなければUTF-16という感じのルールにも見えるが、全部のXMLファイルがUTF-16であるとは限らないのが気持ち悪い。 あといろいろな流儀のファイルが混合しているというのは、上記のXMLで「<?xml」の宣言がないXMLファイルとあるファイルの違いとか、タグ間にスペース/改行が入っているのと入ってないのとか、MSがXMLの出力方法をどうやっているのかが疑問に思えたりする。普通に内部表現でXML用のツリーを作って一箇所で出力するように書いてないのかなぁ。不思議だ。普通にプログラムを書けば同じ流儀になるはずなんだ。たまに適した流儀に変更するというのはあるが(個々の文字をエスケープしないでCDATAにするとか)。 zipに入っているファイル名も「[Content_Types].xml」(なぜ大括弧が必要なの?)とか「_rels/.rels」(なぜにドットファイル??)とか、ちょっと気持ち悪かったりしている。 ファイルの解析としては、トップディレクトリにあるFixedDocSeq.fdseqを見てFixedDocumentSequence/DocumentReferenceのSourceに書いてあるファイル名のファイルにページのリストが書いてあり、1ページ1ページが別々のXMLファイルになっていた。紙を表現するだけあって、かなり細かい指定までできる。すばらしい。最後はodttf(フォントファイル)のパースが一番面倒になるかも。バイナリだしな。 というわけであまり積極的に見ていく気にならない気もするのだが、XPS対応のビューワかコンバータをPython/Tkinterか何かで書ければ、MacやLinux等でXPSが使えてけっこう偉いかもしれないなと思ったりもしている。 しかしもう休暇が終わってしまうのでとりあえずパスだな。いつかやろう。MSがXPSを見捨ててからやるか(笑)

mod_pythonのSession

ここ最近、mod_pythonに凝りはじめてます。 mod_pythonではPHP等と同様、セッション管理を勝手にやってくれる。ということになっているが、デフォルトではセッションCookieにExpiresをつけて送信しないため、ブラウザを閉じるとまたログインし直す羽目になる。そういうわけで1ヶ月くらいは、この状態でセッションを使っていた。 まあそれはそれでポリシーとしては問題ない場合が多いかもしれないけど、「おれはブラウザを閉じてもログアウトしたくないんだよ」という人にとっては迷惑な仕様になる。単に自分だけで使うために簡単な認証をかけているだけなのだが、私の場合は京ぽんのOperaでログインして遊んだりしているが、電話はメールを使うためにもブラウザはすぐに閉じてしまう。そうやっていると毎回パスワードを入力する羽目になり、非常に面倒だった。しかも京ぽんOperaの場合、ユーザ名を漢字かな、パスワードを数字にしとかないと入力がとても… そして私はおもむろに解決策を探し、見つけたのだった。 ちなみにPHPの場合も同じようにデフォルトでExpiresはつかないが、session_set_cookie_params()で設定できるようになっている。リファレンスを見ればすぐに分かるんだね。えらい。 しかしmod_pythonはもっと硬派なので、マニュアル(python.jp)なんか読んでもさっぱり分からない。 以下のようにする。

pgbenchを移植してみたよ…まだ問題は残っているけど

昨日の夜思い立って作りはじめた小さなプログラムが、問題はあるものの、一応動くようになりましたので。 PostgreSQLについてくるpgbenchをPython DB API(python.org)で移植してみた。 興味のある方は、dbench.pyをダウンロードしてみて下さい。 特長は以下の通り。

  • 1つのプログラムでMySQL、PostgreSQL、sqliteに対応 → 同じコード、同じSQL文で測定することで、比較がしやすくなる*はず** pgbenchと同じ負荷をかける(一部制約あり:後述)
  • sqliteの:memory:(メモリDB)でも測定可能(:memory:の場合、-iで初期化後にそのままベンチマークが走るように書いた)

弱点は以下の通りかな。

  • Pythonだからインタプリタ側がネックになるかも → そんなに遅くない気がする。気がするだけ。* 3つのDBMSに対応させるため、SQL文やAPIの呼び出し方に制約がある(後述)
  • MySQL以外では複数コネクションで動かない(致命的。理由の推測は後述)

使い方は以下の通り。引き数を指定しないで実行するとusageが表示されます。 DBの初期化

Python+DBMSメモ

PythonからMySQLdbやpg(PostgreSQL)やsqliteを使う場合、それぞれモジュールがあってそれを通して使うことになっているが、これらはだいたいPython Database API Specification(python.org)に準拠している(日本語訳はこちら(python.jp))ことになっている。 そしてこのAPI仕様では、DBのcursorへのexecuteメソッドでSQLを投げることになっている。私はこのexecuteメソッドに入れる文字列を作る際にescapeしなければ、いわゆる「SQLインジェクションなんとか」が起きるものと理解していた。MySQLdbにはescape_stringが定義されているのはそのためだと。 しかし、違うのですよ。sqliteモジュールにescape_stringメソッドがないので困るなぁと思ってみたけど、実際はexecuteメソッドの第1引き数がフォーマットになっていて、こいつの%s変換で勝手にescapeしてくれるのです(厳密にはparamstyleを設定するとこれ以外の書き方もできる)。例えば、

cppはCと同様、文字列を処理できない

こういう書き方はできません。

#define S "test2"

#if S=="test1"
// test1 code
#elif S=="test2"
// test2 code
#else
// other code
#endif

プリプロセッサなんだから、できてもいいと思う人もいるかもしれないけど、多くの人にはそう思わせないところがC言語の周辺ツールだ。 よい子のみんな、勉強になったかな?

キーワード抽出メモ

名詞と動詞を切り出すのにはchasenを使っている。最初は外部コマンドで呼び出していたが、実行時間がかかるのでPythonのモジュールを作って組み込んだ。モジュールを作るといってもSWIGを使えば簡単で、

// swig -I/usr/include -python chasen.i
// gcc -c chasen_wrap.c -I/usr/include/python2.4 -I.
// g++ -shared chasen_wrap.o -lchasen -o _chasen.so
%module chasen
%{
#include "chasen.h"
%}

%rename(getopt)  chasen_getopt_argv;
%rename(parse)   chasen_sparse_tostr;

%ignore chasen_fparse;
%ignore chasen_fparse_tostr;
%ignore chasen_sparse;

%include "chasen.h"

こんなもんだ。それから、普通にchasenをインストールした状態では英単語や数値を切り出せない。/usr/etc/chasenrcか~/.chasenrcに追加する。

(連結品詞 ((記号 アルファベット)))
(連結品詞 ((名詞 数)))

あとは「単語」→「単語の含まれる文書数」の連想配列を作って一応pickle化して保存しておく。そんなにパワフルなマシンじゃなくて、だいたい1万文書につき40秒くらいかかってしまうので、毎回計算するわけにはいかない。あとは文書ごとに同じようにchasenで名詞と動詞を切り出して、それぞれの単語につき「単語が文書に出現する数*math.log(全文書数/単語の出現する文書数)」をスコアにして、トップ10を計算。一応キャッシュとして保存しておいて、ページのほうではキャッシュデータを元に表示。 全文書に関わる情報を毎回更新せずにやると、division by zeroが発生する。一応この場合はスコア0ということで表現している(本当は間違いで、かなり高いスコアがつくはず)。 たぶん、ちゃんとやればほぼ全部を動的にできると思うんだけどね。全文書に関わる情報をDBに保存しておいて、ページを更新するたびにその情報を更新しておく。あとは表示するときにその文書に関わる情報を見てTF-IDF法で計算したスコアを元にキーワードを求めればいい。そこまでがんばる義理まではないだろう。