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
が手に入った。
ghq v0.13.1で `ghq list --vcs git` がoptimizeされたので、gitだけで良い人は、常にこれを使えば高速化されるかもしれません。お試しください。
— songmu (@songmu) 2019年12月4日
最終的な手元の 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
のようなオプションが必要?