Octopress/Jekyllの実行時間をなんとかする

Octopressに移行してさっそくつまづいた。HTML生成にうんと時間がかかる。どのくらいかかるかというと4分ちょっと。

本文がHTML(拡張子が.none)のファイルが2,188個、markdownファイルが1個。それとその他の記事以外のファイルが10個前後。あとはOctopressの標準的な構成のまま。

一つの記事を書いて、その変換結果を確認して、それから公開する。誤字の修正のようなちょっとした変更のたびに4分ちょっと待つのは辛い。最新の記事だと分かっているときならjekyllコマンドに--limit_posts=1を付けて実行すればある程度回避できなくはないが。

そういうわけで、多少なりとも改善できないものかと調べてみた。

まずはruby-profでざっとプロファイルをとってながめる。メソッド名や呼び出され方などからして、コードを読み込まなければならないところは大変そうなのでとりあえず外す。ロジックを見直さなければダメそうなところも同様に外す。それでも手を入れられそうなところがいくらか残った。

手を入れ始める前に、素の状態での実行時間を測っておく。簡単にtimeコマンドを使う。

242.56s user 7.95s system 100% cpu 4:10.51 total 242.12s user 8.05s system 99% cpu 4:10.53 total 246.98s user 8.13s system 100% cpu 4:15.11 total

平均で4:12.05。

まずは正規表現の生成に時間をとられているらしいRubyPants#educate_quotesLiquid::Variable#initializeに手を入れていく。ただ、Liquidのほうは別プロダクトなので、ひとまずモンキーパッチで実装することにした。

いずれも正規表現を生成する//にオプションoを付けただけ。その効果は:

188.43s user 8.07s system 100% cpu 3:16.50 total 186.97s user 8.11s system 99% cpu 3:15.31 total 191.49s user 8.00s system 99% cpu 3:19.80 total

平均で3:17.20となり、ざっと20%ほど実行時間が短くなった。

もう一つ、こういう手の入れ方はちょっとどうかなーと思いつつ、もっと効果的なところがOctopressではなくJekyll::Siteの中にある。全記事を集めた配列を使って、その前後の記事を探している部分で、記事数が多くなると効率の悪さが目立ってくる。

Liquidと同様、モンキーパッチで実装してみた。ただ、この部分、もう少しコードを読み込んで、きちんと構造を理解してから手をつけたほうがよさそうだ。よって、以下のパッチは、ちょっとどうかなー、である。

ここに手を入れることにしたのはJekyll::Post#<=>の呼び出し回数がかなり多かいのに気付いたため。

<=>が使われるのはまずはsortだが、sortが使われている部分ちょっと手を入れるというわけにはいかないように見えた。(正確にはロジック見直しになりそうな感じの部分と、なんだか一見するとおかしなコードに見える部分とがあった。後者は手を入れられるかもしれないが、まだ手をつけていない。)

ただ、それ以上にArray#indexからの呼び出しのほうが割り合いが大きい。そこで<=>を使わせないように強引な手をとった。これには以下の二つを仮定している。

  • sortのときとは違って、オブジェクトの一致を見ればよいはずだ(==ではなくequal?で判定できるはずだ)
  • 問題の配列postsは、いったん記事を読み込んだら変更は行われないはずだ

結果は次のようになった。

87.51s user 8.01s system 99% cpu 1:35.53 total 87.31s user 8.19s system 99% cpu 1:35.52 total 88.71s user 8.00s system 99% cpu 1:36.73 total

平均で1:35.93。素の状態からすると60%強の短縮ができた。念のため生成されたHTMLを素の状態と、最後の状態とで比較して、その内容に違いがないことを確認した。

追記(2012-05-22)

RubyPantsについての改善提案がOctopressの2.1ブランチに取り込まれた(octopress/pull/514)。Liquidのほうではより広い範囲でoオプションを適用する変更が提案されている(liquid/pull/120)。