KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

こんにちは。@kokukumaです。

昨日ふと調べたgitのブランチ名補完が異常に遅い原因について書きたいと思います。

現象

僕のwindows環境としては、conemuを入れて、その中でgitつかっています。

非常に鬱陶しいのが、ブランチの補完が異常に遅いことです。

git checkout ori TAB! TAB! ...................... 遅い。。。

git-complete.bashの中を確認する

とりあえず、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。

参考: VirtualBoxのファイルシステムを10倍速くする ~ find編 ~

gitのコードを確認する

次に、なぜ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に変換できなくなる可能性がでてきます。

  • refs/heads/branch_name -> f62a336
  • refs/tags/branch_name -> 865fe2e
  • git checkout branch_name => f62a336 ? or 865fe2e ?

そこでshorten_unambiguous_refでは、同じshort_nameで表すことができるrefsがあるか確認して、あったら短くしないと動いています。

これによって、for-each-refで出力される結果がすべて、一意にsha1に変換できることを保証しているわけです。

ただ、その存在確認のやり方が、

  • branch_nameにrefs/remotesとかrefs/tagsとかつけてpathをつくる
  • lstatして、そのpathにファイルがあるか確認する
  • open -> read -> 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を削る作戦。

zatu-git-complete.bash

これをすることで、一意にsha1に変換出来ないブランチ名が補完される可能性があります...

でもまぁ、remotesには基本originがつくでしょうし、ブランチ名と同じ名前のタグとかつけない(たぶん)ので大丈夫。

効果は体感で5倍。

zatu-git-complete.bashを手元に保存して、.bashrcとかに以下みたいに書いておけば使えます。

source (保存したpath)/zatu-git-complete.bash

まとめ

なんでwindowsのgitのブランチ補完が遅い原因が分かった。

雑対応で雰囲気速くなった。

こんにちは。突然ですけど、マシン間でのドットファイルの共有ってどうしてますか?

最近ではドットファイルをGitHubやBitBucket上のリポジトリでバージョン管理している人は珍しくありませんし、Dropboxを活用している人もいるようです。何にせよ、インターネットにつながっていれば自分の作業環境を再現できたり、変更を同期できたりするのは非常に便利ですよね。

ところで、これらのファイルを管理するdotfilesプロジェクトのディレクトリ構造や初期化スクリプトの機能などは人によってバラバラなようです。実際に身の回りの数人に聞いてみたのですが、特に他人のdotfilesプロジェクトをベースにしたりせず、自分だけの仕組みを使っている人が多い印象でした。それほど難しい内容ではありませんから、自作の方が自由度が高くて便利なこともあるでしょう。

一方で、他人のドットファイル管理を見ると気づきがあったりします。私自身、以前は独自の仕組みを使っていましたが、最近は「Zach Holman's dotfiles」をカスタマイズして使っています。本稿では他人のdotfilesプロジェクトに乗っかってみてのメリット・デメリットを紹介します。

Zach Holman's dotfiles とは

Zach Holman's dotfilesはStar数3000・Fork数2000と、GitHub上で公開されているdotfilesプロジェクトの中でも有名なものの一つです。

また、作者のZach Holmanさんは5年前にブログ記事「Dotfiles Are Meant to Be Forked(dotfilesはforkされるべき)」という記事を書いて最近でも話題にされたりしています(参考:redditのスレッド)。

この記事自体は賛否両論ある内容なのですが、「リファクタリングやコードの最小化に知見があるソフトウェアエンジニアが200行のグチャグチャな.bashrcを使っていて平気なのが理解できない、他人が再利用できるように整理して公開しよう」という主張はわからなくもありません。

Zach Holman's dotfiles の使い方

このZach Holman's dotfilesを手元で使う方法を紹介します。

新しいマシンにログインしたら、次のようにgit cloneして初期化スクリプトを実行するだけで普段の設定ファイルが使えるようになります。

$ git clone https://github.com/[自分のユーザー名]/dotfiles.git ~/.dotfiles
(略)
$ cd ~/.dotfiles
$ script/bootstrap

  [ OK ] linked /home/hnw/.dotfiles/zsh/zshrc.symlink to /home/hnw/.zshrc
  [ OK ] linked /home/hnw/.dotfiles/emacs.d.symlink to /home/hnw/.emacs.d
  [ OK ] linked /home/hnw/.dotfiles/tmux/tmux.conf.symlink to /home/hnw/.tmux.conf
  [ OK ] linked /home/hnw/.dotfiles/ruby/gemrc.symlink to /home/hnw/.gemrc
  [ OK ] linked /home/hnw/.dotfiles/ruby/irbrc.symlink to /home/hnw/.irbrc
  [ ? ] File already exists: /home/hnw/.profile (profile.symlink), what do you want to do?
        [s]kip, [S]kip all, [o]verwrite, [O]verwrite all, [b]ackup, [B]ackup all B
  [ OK ] moved /home/hnw/.profile to /home/hnw/.profile.backup
  [ OK ] linked /home/hnw/.dotfiles/bash/profile.symlink to /home/hnw/.profile
  [ OK ] moved /home/hnw/.bash_logout to /home/hnw/.bash_logout.backup
  [ OK ] linked /home/hnw/.dotfiles/bash/bash_logout.symlink to /home/hnw/.bash_logout
  [ OK ] linked /home/hnw/.dotfiles/atom.symlink to /home/hnw/.atom
  [ OK ] linked /home/hnw/.dotfiles/git/gitconfig.symlink to /home/hnw/.gitconfig
  [ OK ] linked /home/hnw/.dotfiles/git/gitconfig.local.symlink to /home/hnw/.gitconfig.local
  [ OK ] linked /home/hnw/.dotfiles/git/gitignore.symlink to /home/hnw/.gitignore
  [ OK ] linked /home/hnw/.dotfiles/git/git_templates.symlink to /home/hnw/.git_templates
  [ OK ] linked /home/hnw/.dotfiles/vim/vimrc.symlink to /home/hnw/.vimrc
  [ OK ] linked /home/hnw/.dotfiles/system/inputrc.symlink to /home/hnw/.inputrc
  [ OK ] linked /home/hnw/.dotfiles/system/dir_colors.symlink to /home/hnw/.dir_colors

  All installed!
$

ホームディレクトリに大量のシンボリックリンクが作られるという、ありがちな挙動です。既存ファイルを上書きするか、バックアップファイルするかなど聞いてくれるのは親切ですが、自作できないほど高機能というわけでもありません。

また、Macの場合は初期化スクリプト中でHomebrewからgrcsparkをインストールされたりします。試す前にbootstrapの中身を追いかけておいた方が良いでしょう。

Zach Holman's dotfiles のよいところ

既に回っている仕組みに乗っかれる

私が自前のdotfilesプロジェクトを管理していたころは、当初は管理対象としてファイルしか想定しておらず、後から~/.atom/のようなディレクトリを管理したくなったタイミングで初期化スクリプトを書き直す、などということがありました。

既存のdotfilesプロジェクトであれば既に多くの人のニーズに応えているため、後から不満が出てくるようなことは滅多にないはずです。

また、初期化スクリプトも自作出来る程度の内容とはいえ、既に多くの人の手元で動いているものを使えるのは安心感があります。過去に私が自作したスクリプトは手元の環境に依存していたりして、新しい環境で動かずにデバッグする羽目になったりしました。

新しい環境で普段と同じドットファイルを素早く使う目的であれば、どの環境でも即座に動くのは地味ながら重要です。その意味で、他人の成果物を利用するのは良い手のように思います。

ドットファイルを整理して管理できる

Zach Holman's dotfilesの特徴的なところは、関係の深い設定を同じサブディレクトリにまとめて管理するというポリシーです。

たとえば私の~/.dotfiles/gitには次のようなファイルがあります。

-rw-r--r--  1 hnw staff  616  4 26 11:37 aliases.zsh
-rw-r--r--  1 hnw staff  289  4 26 11:37 completion.zsh
drwxr-xr-x  3 hnw staff  102  6 26 00:08 git_templates.symlink
-rw-r--r--  1 hnw staff  658  9 23 17:48 gitconfig.local.symlink
-rw-r--r--  1 hnw staff 1395 10 19 12:20 gitconfig.symlink
-rw-r--r--  1 hnw staff  179  4 26 11:56 gitignore.symlink

このように、gitの設定ファイルとzshの設定ファイルの断片とが共存しています。特定のプログラムに関する設定が同じディレクトリにまとまっていると多少は保守性が上がるように思います。

また、この仕組みに乗っかることで長大だったzshの設定ファイルが多少整理されたのも事実です。

以前自分でdotfileを管理していたころは設定を複数マシンで共有することだけが目的で、整理するという観点は無かったので、その意味では良かったと言えそうです。

gitサブコマンドがすごい

実は私がZach Holman's dotfilesを導入して一番気に入ったものは設定ではなく、git wtfという独自サブコマンドだったりします。これは、gitのワーキングディレクトリ上で打つと今どういう状況かを教えてくれるものです(ソースコード:dotfiles/bin/git-wtf)。

$ git wtf
Local branch: master
[x] in sync with remote
Remote branch: origin/master (git@github.com:hnw/dotfiles.git)
[ ] NOT in sync with local (you should merge)
    - init-loader.elでinit.elを分割 [39bece5]
$

たとえば上の出力例は、git fetchだけしてgit mergeしていない状況です。ローカルリポジトリとワーキングディレクトリの同期が取れてないよ、というのをズレているコミットのコミットログと合わせて教えてくれています。他にもgit push -fして歴史がひどいことになったときなど、状況を把握するのに便利だと思います。

他にも独自のgitサブコマンドや他の便利コマンドがbin/以下に含まれており、$PATHに入れて使う前提になっています。設定と密接に関わるようなスクリプトをドットファイルと一緒に管理するのも良いアイデアですよね。

Zach Holman's dotfiles のイマイチなところ

大げさすぎる

このZach Holman's dotfilesの狙いはわかりますが、得られるメリットに対して若干大げさな印象があります。

確かに、設定ファイルをディレクトリ分けして整理できるのは良いことですが、その代わりにファイル数が増え、ディレクトリ階層が深くなってしまいます。ファイル数が増えると心理的にもメンテナンスのハードルは上がります。また、「この設定どこに入れりゃいいんだ?」みたいな状況になって悩みが増えたりもします。

公開したくない設定の扱いが示されていない

設定ファイルを公開しようとするプロセスで、グチャグチャだった設定ファイルが他人に見せられる程度に整理されるというのは確かにメリットかもしれません。

しかし、設定ファイルは整理さえすれば公開できるというものではありません。外部に公開していないホスト名など、設定ファイル中には公開に向かない内容も含まれているはずです。こうした公開できない設定をどう取り扱うかがZach Holman's dotfilesでは示されていません。

このあたりがクリアにならないと、「メリットはわからなくもないけど面倒だからプライベートリポジトリで管理しよう」という発想になる方が自然な気がします。

この記事を読んで「いっちょdotfilesリポジトリ作るかな」と思った方も、公開すべきでない内容をうっかり公開しないよう、注意してください。ちなみに私は公開できない情報は別ファイルにしてgit update-index --skip-worktreeでcommitの対象にしないようにしていますが、あまりスマートではないと感じています。

まとめ

  • Zach Holman's dotfilesというdotfilesプロジェクトを紹介しました
  • 初期化スクリプトや命名ルールなど、細かい点が既に決まっているのは楽です
  • 確かに設定ファイルを整理するきっかけになりました
  • 大げさすぎる印象は否定できません
  • 公開すべきでない設定を公開しないよう注意してください

そもそも論として、他人の環境を使ってみると何かしら気づきがあると思うので、まずは試してみるだけでも価値があると思います(環境設定まわりは時間泥棒なので、ほどほどが大切ですが…)。

今回紹介したZach Holman's dotfilesは合わなさそうだなと思われた方も、dotfiles.github.ioで紹介されている他のdotfilesプロジェクトの中には合うものがあるかもしれません。良いものがあったら私にも教えてください!


@hnw

皆様は、メンターにマウスを禁止されたことはありますか!?

新卒入社1年目のshotaです!

私は恥ずかしながら最近までvimの存在を知らなかったのですが、とあるきっかけがあり3ヶ月程前にVimを触り始めてみました!その期間で、どのようにvimに歩み寄ったかを紹介します。

今までvimに触れたことが無く、これからvimを触りたい!と考えてる方の助けになればと思います。

第一印象

入社した当時はvimというエディタの存在を知りもしませんでした。

というのも学生時代はDirectXやOpenGLといったグラフィックAPIをVisualStudio等の統合開発環境で主に作業をしていたため、CLIに触れる機会も無くエディタに対して何一つ不自由を感じたことがなかったからです。

そんな自分が初めてvimを触れたのは入社後の研修中のことでした。その時の印象を箇条書きで書きますと

  • なんでマウス使わないの?
  • hjklでカーソル移動?誰得?
  • 普通に入力できないの?
  • モードとかややこしいだけじゃない?
  • 使いにくい。。。

と、正直な話最初はネガティブな印象しかないエディタでした。。

きっかけ

入社するまではUnix/Linuxのターミナルを触る事はありませんでしたし、今後も触るつもりがなかったので「別にvimで無くとも文字は書けるしこんな使いづらいエディタ使わなくてもいいかー」と高を括っていたのですが、親しい同僚達にはvimを利用している方が多く、魔法のように文字を入力している姿を見て衝撃を受けました。

「ここまで出来るようになればかっこいいなぁ」と感じて、自分もこの領域まで到達してやる!とvimを使う決意をしたのです。(要するに使いこなすとかっこよさそうだったから!)

3ヶ月間で取り組んだこと

エディタは必ずvimを使用する

vimを触る決意をしてまずはじめに取り組んだのが、何を書くにも日報を書くにもちょっとしたメモを残すにもとりあえずvimを使用するということです。

ちょうどその時期は案件に配属されてから、1週間を利用して簡単なゲームを制作するというタスクを課せられていました。

その時点ではインサートモードに入り文字を入力してノーマルモードに戻るぐらいの知識しかなく、vimに対する感想は随所でアピールしているのですが「漫画ドラゴンボールの悟空の胴着」です。この頃はどうにも通常のキーバインドのエディターの方が効率がよかったです。。

vimとの格闘の中で躓いたりわからないことがあればすぐに調べることで、vimに対する知識を実用レベルでどんどん吸収することができたので、これからvimを触る方にはぜひ実践していただきたいです。

マウスを極力触らないようにする

私はwindows用のvimを利用させていただいていたので、実はマウスが使えます。

vimというエディタはマウスがまだ一般的でなかった時代に作られたviと呼ばれるエディタを元に制作されたという話を聞いてマウスを極力触らないよう意識をするようになりました。

元々マウスを多用する方だったので少し気を抜くとマウスに手が伸びてしまい。この取り組みには大変苦労しました。

しかしこの苦労を乗り越えてh,j,k,lでの移動や移動に便利なコマンド等を活用できるようになり、今ではマウスに手を伸ばすこと自体が億劫になっています!

一日一つ便利なコマンド等を覚える

vimを触り始めてから最初の1ヶ月あたりは1日に1つずつ便利そうなコマンドを覚えていきました。

物覚えが悪い自分にはこの方法が実に効果的で、この時の知識が今でも業務で活躍しています。

その中から5つピックアップして紹介いたします。

「*」

as

単純ですがとても強力でした。

任意の単語の上で「*」を入力すると、その単語で検索を掛けてハイライトをしてくれます。その後「N」,「n」でそれぞれ前方後方の単語へジャンプできます。

「f,F,t,T」

fFtT

自分は主に行中の移動で利用しています。

「f」の後に任意の1文字を入力すると次に出てくる文字の上に、「t」の場合は文字の手前にカーソルがジャンプします。

それぞれ大文字の場合はジャンプの方向が前方になり、直近にジャンプした文字は「;」で次へ「,」で前へジャンプできます。

「◯i◯,◯a◯」

diw

動作+a or i+対象」という使い方をして、対象に対して動作を行います。

例えば「vi”」の場合。「区切り文字を含めない”で囲まれたテキストをビジュアルモードで選択する」というコマンドになります。

このとき「i」を利用すると対象に区切り文字を含めず、「a」を利用すると対象に区切り文字を含むことができます。

「i」や「a」は挿入モードに切り替えるiやaと違いそれぞれinnerと冠詞のaという意味を持つそうです。

また対象を「w」とすることで単語を対象にすることができます。

「J,gJ」

gJ

ビジュアルモードで選択した行を連結することができます。

「J」の場合連結時にスペースが挿入されますが、「gJ」を利用することでスペースを挿入せずに連結することができます。

「ctrl+v」

ctrlv

こちらのコマンドはビジュアルモードで矩形選択をするものです。

また矩形選択時に「I」,「A」どちらかで挿入モードに入り文字を入力したあとノーマルモードに戻ると選択した行すべてに同じ入力ができます。

前述の「J」,「gJ」を併用して改行区切りのデータを手軽にCSVに整形できるので、業務でもよく使っています。

.vimrcにキーバインドを記述する

2ヶ月目に入り操作にも慣れてきたころ、.vimrcというファイルに記述をすれば思い通りに設定ができることを思い出したので、前々から使い辛いと思っていた以下の4つのキーバインドを変更しました。

  1. 行頭へジャンプ:「0」→「ctrl+h」
  2. 行末へジャンプ:「$」→「ctrl+l」
  3. 画面上の先頭行へジャンプ:「H」→「ctrl+k」
  4. 画面上の末尾行へジャンプ:「L」→「ctrl+j」

最初にhjklのキーバインドに矯正したこともあり、こちらのバインドを設定するだけでカーソルのジャンプがとても直感的になりました。

何よりこのバインドではホームポジションからほとんど指を動かす必要が無くなるので素早く操作ができるようになります。

また新バインドのうち、1,2,4については元々別の機能がバインドされていますが、1,4はctrlを押さずに入力した場合と同じ機能がバインドされていて、2についても画面の再描画という今まで使ったことがない機能がバインドされています。

今のところ使う場面に出くわしていませんので、必要になれば使わなくなった「0」や「$」にバインドしようと考えています。

これから

3ヶ月間で標準のVimにはそこそこ慣れてきました。

ですが実はまだvimの本領ともいえるであろうプラグインによる拡張を試せていません。

同期の友人はプラグインでいろいろ拡張してとても便利にしているので、そろそろ3,4ヶ月目に入ることですしいろいろなプラグインを試してみたいなぁ。

しかし標準の状態でもちょっとコマンドを覚えるだけでコーディングの効率が上がりましたし、何より入力が楽しくなりました!

また、vimに慣れてきたといいましても、まだまだ手が覚えておらずやりたい動作を瞬時に入力できませんし知らないコマンドも山ほどあるので時間をかけてもっとお近づきになりたいと思います。

長文ですが最後までお付き合いいただきありがとうございました。

↑このページのトップヘ