こんにちは。@kokukumaです。
昨日ふと調べたgitのブランチ名補完が異常に遅い原因について書きたいと思います。
僕のwindows環境としては、conemuを入れて、その中でgitつかっています。
非常に鬱陶しいのが、ブランチの補完が異常に遅いことです。
git checkout ori TAB! TAB! ...................... 遅い。。。
とりあえず、git-complete.bashの中でどこが遅いのか読んでみます。
雰囲気で、git for-each-ref
が遅いと察することができます。
for-each-refとは、.git内にあるrefsを出力するためのgitコマンドです。詳しくは、for-each-refを参照のこと。
とりあえず、時間を測ってみます。
※ 以下、gitの挙動を知りたいだけなので、linux上で実施。
vagrant@debian-jessie:~/projects/lovelive$ time git for-each-ref --format="%(refname)" refs/tags refs/heads refs/remotes >/dev/null
real 0m0.064s
user 0m0.004s
sys 0m0.032s
vagrant@debian-jessie:~/projects/lovelive$ time git for-each-ref --format="%(refname:short)" refs/tags refs/heads refs/remotes >/dev/null
real 0m1.107s
user 0m0.008s
sys 0m0.496s
なぜか、shortつけただけで、すごく時間が伸びています。
原因を調べるため、straceを使って実行されているシステムコールのsummaryをとってみます。
vagrant@debian-jessie:~/projects/lovelive$ strace -cf git for-each-ref --format="%(refname)" refs/tags refs/heads refs/remotes > /dev/null
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.000907 17 54 openat
0.00 0.000000 0 145 read
vagrant@debian-jessie:~/projects/lovelive$ strace -cf git for-each-ref --format="%(refname:short)" refs/tags refs/heads refs/remotes > /dev/null
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
87.62 0.015765 1 13131 13047 lstat
6.27 0.001128 21 54 openat
でました。
ちょくちょく遅い原因となっているlstat。
次に、なぜshortをつけただけでlstatが大量発行されるのかを、gitのコードを追って調べてみます。
// git-2.1.4/builtin/for-each-ref.c
static void populate_value(struct refinfo *ref)
{
(省略)
if (!strcmp(formatp, "short"))
refname = shorten_unambiguous_ref(refname, // <- ここでrefnameを短くしているぽい。
warn_ambiguous_refs);
// git-2.1.4/refs.c
char *shorten_unambiguous_ref(const char *refname, int strict)
{
(省略)
/* skip first rule, it will always match */
for (i = nr_rules - 1; i > 0 ; --i) {
(省略)
/*
* check if the short name resolves to a valid ref,
* but use only rules prior to the matched one
*/
for (j = 0; j < rules_to_fail; j++) {
(省略)
mksnpath(refname, sizeof(refname),
rule, short_name_len, short_name);
if (ref_exists(refname)) // <- ここの中でlstatしてる。
break;
}
(省略)
}
shorten_unambiguous_refがやっていることは、refsをshort nameに変換する作業です。
例えば、refs/heads/branch_name を branch_name に変換する感じ。
やり方は単に、refs/headsとかに引っかかったら、その部分を削るだけです。
しかし短くすると、ブランチ名を一意にsha1に変換できなくなる可能性がでてきます。
そこでshorten_unambiguous_refでは、同じshort_nameで表すことができるrefsがあるか確認して、あったら短くしないと動いています。
これによって、for-each-refで出力される結果がすべて、一意にsha1に変換できることを保証しているわけです。
ただ、その存在確認のやり方が、
となっているので、1つのrefsに対して、4、5回lstatを実行しています。
そのためレポジトリのブランチが増加すると、lstatの回数が増加して、1TABにつき1万回のlstat発行!とかになります。
そして、gitbashのlstatや、VM(over vboxsf)のlstatが凄く遅いせいで、windowsでブランチ補完が遅くなるという感じ。
for-each-refを上手く直す方が正しいでしょうが、ちょっと大変そうなので、雑な対応で乗り切ることにしました。
:short
つけるのが遅い原因なら、:short
やめてsedでrefs/headを削る作戦。
これをすることで、一意にsha1に変換出来ないブランチ名が補完される可能性があります...
でもまぁ、remotesには基本originがつくでしょうし、ブランチ名と同じ名前のタグとかつけない(たぶん)ので大丈夫。
効果は体感で5倍。
zatu-git-complete.bashを手元に保存して、.bashrcとかに以下みたいに書いておけば使えます。
source (保存したpath)/zatu-git-complete.bash
なんでwindowsのgitのブランチ補完が遅い原因が分かった。
雑対応で雰囲気速くなった。
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。
合わせて読みたい
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。