ghq list を高速化する PR を出した

ghq を使い始めて以来、ディレクトリ移動にしろエディタオープンにしろ ghq list に頼りっぱなしだが、最近どうも遅く感じていた。そこでボトルネックを探してみて特定の状況下で ghq list の実行速度が向上する PR を出してみたところ、無事にマージされた。

具体的には ghq で Git リポジトリだけを使っている時に、ghq list --vcs=git が速くなる。自分の手元では20~30%くらい短縮された。

せっかくなので何を試して最終的に何をやったのかを書き残す。

試したこと

ボトルネックを探すために pprof のフレームグラフを眺めてみたところ、findVcs という関数がそれなりの時間を食っていることがわかった。

findVcs(path string) は引数で渡されたディレクトリに VCS 毎の固有ディレクトリ(.git, .hg など)があるかを、os.Stat で調べる。この時、調べる順番が以下のようになっていた。

  • CVS/Repository
  • .fslckout
  • .git/svn
  • _FOSSIL_
  • _darcs
  • .bzr
  • .git
  • .svn
  • .hg

つまり、Git しか使っていないユーザからすると、必ず空振る os.Statディレクトリ毎に6回実行されるのが遅くなる原因だった。

試しに .git だけを対象にするよう手元で修正したところ、1秒前後かかっていたのが300ms前後短縮された。

PR にする

自分で使うだけなら .git だけを調べるだけでいいが、PR にする際にはそうもいかないので、設定もしくはオプションで自分が使う VCS を指定する方向で考えてみた。

ghq は設定を git config で行うので、新たに ghq.findVcs というオプションを追加する形で PR を出した。

この PR で Songmu さんより「元々 --vcs という指定した VCSリポジトリのみを表示するオプションがあるので、そちらを修正しては」という提案をもらった*1

実は当初 --vcs の修正も考えていたが、以下の理由より git config にした。

  • オプションを毎回指定するのがめんどくさい
  • 複数 VCS を使っているユーザの場合、--vcs を変えるとパフォーマンスが劣化する((例えば --vcs=git が指定された時、Git 以外で管理されているローカルリポジトリの内部までトラバースしてしまう))

前者については ghq list は何かのコマンドやエイリアス内で呼ぶことが多いため杞憂であり*2、後者については --max-depth オプションを実装すれば回避可能だろうという意見をもらったので、改めて --vcs オプションを修正した PR を出した。

こちらがめでたくリリースされ、これまでよりも速い ghq list が手に入った。

最終的な手元の Before/After はこんな感じ。

Before:

real    0m0.844s
user    0m0.786s
sys     0m1.885s

After:

real    0m0.554s
user    0m0.580s
sys     0m1.096s

余談

ところで --max-depth があれば ghq list のパフォーマンス問題は回避できる、と書いたがまだ実装されていない。現時点ではディレクトリトラバースgithub.com/saracen/walker を使っているが、walker の Visitor 関数にはファイルパスしか渡されない。また、内部で goroutine によって並行化されるので、クロージャ外の変数で深さをカウントするのも難しそう。

もし --max-depth を作るとしたら、walker 側に WithMaxDepth のようなオプションが必要?

*1:Songmu さん的にはこれ以上 ghq に設定値を増やしたくないとのこと

*2:少なくとも自分はそう。