elispを眺めてみた
twitterにアクセスするのにtwittering-modeなるものを使っているのだがどうもタイムラインを更新する時に重たい感じがする。しかし、長年Emacsを利用はしているがelispにはとんと疎いのでどこがどう重いのかtwittering-mode.elを眺めても皆目見当がつかない。つーか、twittering-mode.elをどこに置いたのかすら忘れていてだめだめである。/usr/share/emacsのどこかに置いたかと思うのだけど全然見つからない。なんと、/etc/emacsにおいておいた。大丈夫か自分という感じである。(とほほ)
どこで時間がかかっているかを知るには実行プロファイルをとるのが王道中の王道である。elisp performance tuningなどでぐぐるが全然思うように情報が見つからない。多分、絶対elispのプロファイラがあるはずだと思うのだが探し出せない。
なんのことはない、elisp profilerでぐぐって、山本和彦さんのhttp://www.mew.org/~kazu/doc/elisp/profile.html を発見した。そこで早速elpを利用してみた。
M-x elp-instrument-package
ここでtwittering-といれる。パッケージ名の先頭を入れておくと頭がtwittering-な関数すべてがプロファイルの対象になるようである。
M-x elp-results
で結果が "*ELP Profiling Results*" バッファに合計時間で並べ替られて表示される。
Function Name Call Count Elapsed Time Average Time ======================================== ========== ============ ============ twittering-http-get-default-sentinel 19 53.987238 2.8414335789 twittering-render-friends-timeline 19 30.305835000 1.5950439473 twittering-format-status 1129 28.148776999 0.0249324862 twittering-get-response-body 19 15.483321 0.8149116315 twittering-timer-action 19 10.219740999 0.5378811052 twittering-friends-timeline 19 10.217067000 0.5377403684 twittering-http-get 19 9.240164 0.4863244210 twittering-xmltree-to-status 19 7.3414859999 0.3863939999 twittering-status-to-status-datum 380 7.2881300000 0.0191792894 twittering-cache-status-datum 380 0.4088780000 0.0010759947 twittering-decode-html-entities 2280 0.4062390000 0.0001781750 twittering-image-type 1129 0.1648270000 0.0001459937 twittering-get-response-header 19 0.1051270000 0.0055330000 twittering-get-or-generate-buffer 132 0.0171760000 0.0001301212 twittering-buffer 20 0.013266 0.0006633 twittering-http-buffer 76 0.0075500000 9.934...e-05 twittering-wget-buffer 36 0.0067700000 0.0001880555 twittering-mode 1 0.00591 0.00591 twittering-mode-init-variables 1 0.004641 0.004641 twittering-start 1 0.000443 0.000443
上位3つの関数で6割以上の実行時間を消費している。
そこで当該関数を写経する。
(defun twittering-http-get-default-sentinel (proc stat &optional suc-msg) (let ((header (twittering-get-response-header)) (body (twittering-get-response-body)) (status nil) ) (if (string-match "HTTP/1\.[01] \\([a-z0-9 ]+\\)\r?\n" header) (progn (setq status (match-string-no-properties 1 header)) (case-string status (("200 OK") (mapcar #'twittering-cache-status-datum (reverse (twittering-xmltree-to-status body))) (twittering-render-friends-timeline) (message (if suc-msg suc-msg "Success: Get."))) (t (message status)))) (message "Failure: Bad http response."))) )
defunは関数を定義するあれだ。(defun 関数の名前 (引数のリスト) 関数の定義)みたいな形式になる。この例ではtwittering-http-get-default-sentinelが関数の名前で(proc stat &optional suc-msg)が引数のリストで(let ... )が関数の定義だ。
次のletというのはlet (bindings...) forms...という形式をとる。
ここの(bindings...)は下記だ。
((header (twittering-get-response-header)) (body (twittering-get-response-body)) (status nil))
headerを(twittering-get-response-header)に、bodyを(twittering-get-response-body)そしてstatusをnilにする。まあ、ローカル変数に代入するようなものか。
で次がforms...に対応するところで、関数の本体みたいなものか。
(if (string-match "HTTP/1\.[01] \\([a-z0-9 ]+\\)\r?\n" header) (progn (setq status (match-string-no-properties 1 header)) (case-string status (("200 OK") (mapcar #'twittering-cache-status-datum (reverse (twittering-xmltree-to-status body))) (twittering-render-friends-timeline) (message (if suc-msg suc-msg "Success: Get."))) (t (message status)))) (message "Failure: Bad http response."))
ということで(if ...)がなにやらやっていることがわかる。まあ通常のプログラミング言語を知っていればif文というかifの構文が何をするかは大体わかるかと思う。
elispでのifはif 条件 then-form else-forms...という形式だ。
(string-match "HTTP/1\.[01] \\([a-z0-9 ]+\\)\r?\n" header)が条件だ。headerがHTTP....の形式になっているか調べている。もしこの条件が真ならば、下記を実行する。
(progn (setq status (match-string-no-properties 1 header)) (case-string status (("200 OK") (mapcar #'twittering-cache-status-datum (reverse (twittering-xmltree-to-status body))) (twittering-render-friends-timeline) (message (if suc-msg suc-msg "Success: Get."))) (t (message status))))
偽だったら(message "Failure: Bad http response.")を実行する。ここでmessageというのはその名のとおり、メッセージを表示する関数である。
さて、(progn...)の中身を見ていこう。prognは、そこにあるlisp関数を順番に実行するようなものである。ここの例では、(setq...)(case-string...)を実行する。
setq status (match-string-no-properties 1 header)
setqはstatusを(match-string-no-properties 1 header)を評価した値にする。さてmatch-string-no-propertiesは最後の探索や一致操作で一致したテキストを文字列として返す。1というのは1番目の括弧で囲んだ部分式に対応する部分のみを返す。 \\([a-z0-9 ]+\\)のところだ。
case-stringというのは何か。elispの関数ではない。
(defmacro case-string (str &rest clauses) `(cond ,@(mapcar (lambda (clause) (let ((keylist (car clause)) (body (cdr clause))) `(,(if (listp keylist) `(or ,@(mapcar (lambda (key) `(string-equal ,str ,key)) keylist)) 't) ,@body))) clauses)))
というマクロが定義されていた。
うう〜〜〜一気に難しくなったなあ。ということでマクロの定義のところままで、写経したtwittering-modeの読書はひとまず休憩とする。