「立て!立つんだビムー!」
この記事は Vim アドベントカレンダー 2012 の 19 日目の記事です. 昨日はhrsh7th さんの vim-versions についての記事 でした.
たくさんプラグインを入れたり設定を書いたりしていると Vim の立ち上がりはどんどん遅くなってしまいます. 一度 Vim を立ち上げたらそのあと閉じることが無いスタイルの人はそれほど気にならないかもしれませんが, シェルからターミナル内で Vim を開いたり閉じたりするスタイルの人にとっては起動速度はとても大事です.
今年のアドベントカレンダーでは,素敵なプラグインを入れて Vim の機能を強化する記事がたくさん紹介されているので, ここではそういった便利さをなるべく維持しつつ,起動時間を抑える方法を紹介します.
(1/3 追記) neobundle.vim がバージョン 3 になり,ファイルタイプ・コマンド・関数名・マッピングのいずれかで読み込みタイミングを指定する機能が追加されました.
アウトライン
- 要らないプラグインを取り除く
- 要らない設定を取り除く
- autoload 以下の関数をなるべく使わないようにする
- プラグインの読み込み自体を遅延する(neobundle.vim 編)
- NeoBundleLazy の autoload オプションを使う (1/3 に追記)
- 特定の filetype でのみ読み込む
- gVim のみで読み込む
- 特定のコマンドを使った時に初めて読み込む
- プラグイン化して autoload に移す
- augroup を1つに統一する
- 時間のかかる処理をなるべく遅延する
- 読み込む vimrc を制限する
- vimrc 読書会に参加する
- まとめ
要らないプラグインを取り除く
色々プラグインを試していると,「これ良さそうだなー」と思って入れてみて結局あまり使わなかったプラグインなどがどうしても出てきてしまいます. そういったプラグインを見つけて取り除くことで,起動時間を改善できます.
どのプラグインの読み込みにどの程度の時間がかかっているかは --startuptime
オプションを付けて起動することで
確認できます.
vim --startuptime hoge
として Vim を起動すると,Vim が起動時のファイルごとの読み込み時間がカレントディレクトリの hoge というファイル に書き込まれます. 第1カラムに起動開始からの時間(ミリ秒),第2カラムにファイルの読み込みにかかった時間(ミリ秒)が出ているはずです. あとはそれを見て,読み込み時間がかかっていて,かつほぼ使っていないプラグインを削っていきます.
要らない設定を取り除く
次に vimrc のどの部分にどれぐらい読み込み時間がかかっているかを調べます.
上記の --startuptime
オプションではファイルごとの読み込み時間しか分からないため,.vimrc の各行の読み込みにどれくらい
かかっているかを知るためには mattn さんの書いた benchvimrc または +profile 付きでビルドされた Vim を使います.
- benchvimrc → vimrcのどこが重いのかを調べられるプラグイン書いた。
- +profile を使う → vimrc のどこが重いのかを調べるもう1つの方法
.vimrc の各行と,それぞれの行の読み込みにかかった時間が分かるので,それを元にあまり使っていなくて,かつ読み込みに時間の かかっている部分を削っていきます.
ここまでで,まずは「あまり有用ではないのに読み込みに時間をかけてしまう」部分を効率的に見つけて削る方法を示しました. 次からは,プラグインや設定の機能を損なわずになるべく起動時の読み込み時間を抑える方法について,もう少し細かい部分 を見ていきます.
autoload 以下の関数をなるべく使わないようにする
Vim の関数で名前に #
が入っているものは,各プラグインの autoload ディレクトリ内に定義されている関数です.
例えば,unite#do_action()
は autoload/unite.vim
ファイルの中に定義されている関数になります.
autoload 以下のファイルは実際に関数が呼ばれ,ロードする必要が生じたときに初めてロードされます.
詳しくは :help autoload
を参照してみて下さい.
.vimrc 内においても autoload 以下で定義された関数をなるべく呼び出さないようにすることで,余計な autoload 以下のファイル の読み込みを減らし,.vimrc の読み込み速度を上げることができます.
実際に VimShell での例を見てみます. VimShell では,拡張子とコマンドを紐付けておくことができます.
let g:vimshell_execute_file_list = {} call vimshell#set_execute_file('txt,vim,c,h,cpp,d,xml,java', 'vim')
とすると,例えば ./hoge.c
と実行するだけで vim コマンドが実行されてそのファイルを開けます.
しかし,この設定は vimshell#set_execute_file()
を呼び出しているため,この時点で autoload/vimshell.vim の読み込みが必要になります.
この関数がやっているのは,各拡張子をキーに持ちそれに対応するコマンドを値に持つ辞書を変数 g:vimshell_execute_file_list
に定義している
だけです.よって,次のように書きかえることができます.
let g:vimshell_execute_file_list = \ { 'txt' : 'vim', 'vim' : 'vim', 'c' : 'vim', 'h' : 'vim', \ 'cpp' : 'vim', 'xml' : 'vim', 'java' : 'vim' }
これだと 'vim'
の繰り返しが多すぎて煩わしいという人は for 文を使うともう少し綺麗に書けます.
let g:vimshell_execute_file_list = {} for ext in split('txt,vim,c,h,cpp,d,xml,java', ',') let g:vimshell_execute_file_list[ext] = 'vim' endfor
vimshell#set_execute_file()
を呼び出さないようにすることで,Vim 起動時に autoload/vimshell.vim をロードせずに済みます.
プラグインの読み込み自体を遅延する(neobundle.vim 編)
プラグインによっては,特定の状況下でのみ力を発揮するものがあります.例えば clang_complete という C++ のコードを静的に 解析して補完候補を生成するプラグインは,当然ながら C++ のコーディングをしているときしか使いません. そういったプラグインを常にロードする必要はありませんし,起動時にロードする分は無駄になってしまいます.
そこで,プラグインマネージャの力を借りて,本当にそのプラグインが必要になるまで読み込みを遅延します. 僕はプラグイン管理に neobundle.vim を使っているので,neobundle.vim を 使った場合について書きます.pathogen や vundle,unbundle などを使った場合できるのかどうかは各プラグインのヘルプを参照して下さい.
NeoBundleLazy の autoload オプションを使う (1/3 に追記)
neobundle.vim ver 3 から NeoBundleLazy
コマンドに autoload
オプションが追加されました.
これにより,プラグインの読み込みタイミングを
- 特定のファイルタイプが読み込まれた時
- 特定のコマンドを使用した時
- 特定の関数が呼ばれた時
- 特定のマッピングが実行された時
のいずれかまで遅延することが出来るようになりました.
以下は neobundle.vim の help に書いてある例です.
" ファイルタイプが c か cpp のファイルを読み込む時に clang_complete をロードする NeoBundleLazy 'Rip-Rip/clang_complete', { \ 'autoload' : { \ 'filetypes' : ['c', 'cpp'], \ }, \ } " TweetVimHomeTimeline コマンドを使う時に TweetVim をロードする NeoBundleLazy 'basyura/TweetVim', { 'depends' : \ ['basyura/twibill.vim', 'tyru/open-browser.vim'], \ 'autoload' : { 'commands' : 'TweetVimHomeTimeline' }} " <Plug>(smartword-w),<Plug>(smartword-b),<Plug>(smartword-ge) のいずれかのマッピングを実行する時に vim-smartword を読み込む NeoBundleLazy 'kana/vim-smartword', { 'autoload' : { \ 'mappings' : [ \ '<Plug>(smartword-w)', '<Plug>(smartword-b)', '<Plug>(smartword-ge)'] \ }} " vcs#info() 関数か Vcs コマンドを実行しようとしたときに vim-vcs を読み込む NeoBundleLazy 'Shougo/vim-vcs', { \ 'depends' : 'thinca/vim-openbuf', \ 'autoload' : {'functions' : 'vcs#info', 'commands' : 'Vcs'}, \ }
詳細については :help neobundle-options-autoload
を参照してみて下さい.
特定の filetype でのみ読み込む
neobundle.vim バージョン 3 から,autoload オプションで指定できるようになったため,このセクションは不要になりました
先ほどの clang_complete のような例です.NeoBundleLazy でプラグイン登録だけを行なっておき,FileType イベントで実際に 対象のファイルタイプのファイルが読み込まれた時に初めてプラグイン本体を読み込むようにします.
僕が実際に設定している C++ の例です.
" プラグインとして登録されるだけでまだ読み込まれない " C++11 対応シンタックスファイル NeoBundleLazy 'vim-jp/cpp-vim' " clang を使った静的コード解析 NeoBundleLazy 'Rip-Rip/clang_complete' " ISO 直近のドラフト N3337 を閲覧するための unite ソース NeoBundleLazy 'rhysd/unite-n3337' " ファイルタイプが cpp なファイル(= C++ ソースコード)が読み込まれたときにプラグインを読み込む augroup NeoBundleLazyLoadCpp autocmd! autocmd FileType cpp NeoBundleSource \ cpp-vim \ clang_complete \ unite-n3337 augroup END
gVim のみで読み込む
errormarker.vim のように GUI でしか機能しないプラグインや カラースキームの読み込みは CUI の Vim では無意味です. 下記の例では,GUI でしか使わないカラースキームを gVim 起動時のみ読みこむようにしています.
" .vimrc に記述 NeoBundleLazy 'ujihisa/unite-colorscheme' NeoBundleLazy 'tomasr/molokai' NeoBundleLazy 'altercation/vim-colors-solarized' NeoBundleLazy 'earendel' NeoBundleLazy 'rdark' NeoBundleLazy 'telamon/vim-color-github' " .gvimrc に記述 NeoBundleSource unite-colorscheme \ molokai \ vim-colors-solarized \ earendel \ rdark \ vim-color-github
特定のコマンドを使った時に初めて読み込む
neobundle.vim バージョン 3 から,autoload オプションで指定できるようになったため,このセクションは不要になりました
特定のファイルタイプに依存しない,gVim と 端末内の Vim の両方で使うプラグインであっても, まだ読み込みを遅延する方法があります.
例えば,VimFiler はとても便利で GUI と CUI を問わずでも使用しますが,ファイル操作をしたいときにしか使いませんし, 読み込みに結構時間がかかるのが気になります. そういった場合は,コマンドを関数でラップしてしまい,最初にコマンドが呼ばれた時にプラグインをロードする処理を追加 する方法があります. これは daisuzu さんの vimrc で実際に実装されています.
https://github.com/daisuzu/dotvim/blob/master/.vimrc#L2212
VimFiler のコマンドを LoadVimFiler()
という関数でラップし,LoadVimFiler()
内では初回呼び出し時に
VimFiler 本体をロードする処理を行なっています.
プラグイン化して autoload に移す
プラグイン化するほどでもない機能を vimrc 内に実装することはよくありますが,それに色々付け足していくことでだんだん
実装が大きくなり読み込みに時間がかかってくるようになることがあります.
その部分を抜き出し,新しいプラグインの autoload に移してしまうことで,その部分の読み込みを抑えることができます.
clever-f.vim は最初は vimrc 内で
f
マッピングを拡張するために書かれたものでしたが,実装が大きくなってきた & f
を実際に使うまでは読み込みは
不要なのでプラグインとして切り出し,関数を autoload に移しました.
augroup を1つに統一する
「要らない設定を取り除く」の章で説明した方法で実際に vimrc の読込状況を眺めた方はお気づきかもしれないですが,
autocmd!
は地味に時間のかかるコマンドです.
よって,余分な augroup
を作らず,1つのグループにまとめることで少し改善することができます.
例えば,次のような設定があった場合,
augroup SettingCpp autocmd! autocmd FileType cpp inoremap <buffer>;; :: augroup END augroup SettingRuby autocmd! autocmd FileType ruby inoremap <buffer><C-s> self. augroup END augroup SettingHaskell autocmd FileType haskell nnoremap <buffer><silent><Leader>ht \ :<C-u>call <SID>ShowTypeHaskell(expand('<cword>'))<CR> function! s:ShowTypeHaskell(word) echo join(split(system("ghc -isrc " . expand('%') . " -e ':t " . a:word . "'"))) endfunction augroup END
という,それぞれのファイルタイプごとに augroup を作るのでは無く,
" 最初に vimrc 全体で使う augroup を定義しておく augroup MyVimrc autocmd! augroup END " ... " FileTypeDetect グループに追加したい autocmd イベントを記録 autocmd MyVimrc FileType cpp inoremap <buffer>;; :: autocmd MyVimrc FileType ruby inoremap <buffer><C-s> self. autocmd MyVimrc FileType haskell nnoremap <buffer><silent><Leader>ht \ :<C-u>call <SID>ShowTypeHaskell(expand('<cword>'))<CR> function! s:ShowTypeHaskell(word) echo join(split(system("ghc -isrc " . expand('%') . " -e ':t " . a:word . "'"))) endfunction
とすることにより,autocmd!
の回数を減らすことができました.
ただし,autocmd! で後にクリアすることを前提に考えられている augroup (例えば後で出てくる UniteCustomActions
)
ではイベントをクリアするためにグループを切らなければならないのと,augroup を特定の autocmd コマンド群のネームスペースの
ように使っている場合は可読性がやや低下する場合があるため,導入する際はそのあたりを考慮すべきです.
時間のかかる処理をなるべく遅延する
あまり広く使える方法では無いですが,時間のかかる処理を特定の autocmd イベントが発生するまで遅延
する方法があります.考え方としては,NeoBundleLazy
のときと同じです.
unite#custom_action()
の実行を遅延する例を挙げます.
unite#custom_action()
は特定の種類の候補に対して自作のアクションを追加する関数です.
Mac の Finder でディレクトリを開くアクションはこんな感じで .vimrc 内に書けます.
" Finder action for Mac if has('mac') let finder = { 'description' : 'open with Finder.app' } function! finder.func(candidate) if a:candidate.kind ==# 'directory' call system('open -a Finder '.a:candidate.action__path) endif endfunction call unite#custom_action('directory', 'finder', finder) endif
しかし,unite アクションは実際に unite や vimfiler を立ち上げるまで必要になりませんし,
unite#custom_action()
の実行にも結構時間がかかります.
そこで,FileType イベントを利用して,実際に unite.vim や vimfiler が読み込まれるまで
上記のコードが実行されないようにします.
" 遅延したい処理を関数として定義しておく function! s:define_unite_actions() " Finder for Mac if has('mac') let finder = { 'description' : 'open with Finder.app' } function! finder.func(candidate) if a:candidate.kind ==# 'directory' call system('open -a Finder '.a:candidate.action__path) endif endfunction call unite#custom_action('directory', 'finder', finder) endif " 一度読み込むともうこの関数は呼ばなくて良いのでイベントをクリアしておく autocmd! UniteCustomActions endfunction " filetype が unite か vimfiler のときにカスタムアクションを初めて定義する augroup UniteCustomActions autocmd! autocmd FileType unite,vimfiler call <SID>define_unite_actions() augroup END
読み込む vimrc を制限する
「要らないプラグインを取り除く」の章で紹介した方法で Vim 起動時のファイルの読み込み状況を眺めた方はお気づきかもしれないですが, Vim はデフォルトでシステムの vimrc とユーザの vimrc の両方を読み込みます. もしシステムのほうの vimrc を読みこまなくても良ければ,次のようなコマンドで Vim を起動することで,読み込む vimrc をユーザのもの のみに制限できます.
vim -u $HOME/.vimrc
ただし,システムの vimrc の中にも有用な設定があるので,本当に読み込まなくて良いのか,ちゃんと中身を確かめておくべきです.
vimrc 読書会に参加する
lingr の Vim 部屋 にて,毎週土曜日の 23:00 から vimrc 読書会 が開催されています. 毎週誰かの vimrc を題材にしてあーだこーだ言う会です.他の人の vimrc はとても参考になりますし, Vim に詳しい方々も多く参加されているので,色々質問する機会でもあります.
まとめ
今回の記事は自分が実際に行った改善を元に書いたので,どれくらい参考になるかは分かりませんが,少しでも Vim の起動速度改善に貢献できたなら幸いです. 普段 vimrc の見直しを特にしないという方も,vimrc の掃除をしてみてはいかがでしょうか.年末ですし.
参考までに上記の改善を行なうことによって,Vim の起動時間は下記のように変化しました.
- before: 454 ms → after: 364 ms
明日のアドベントカレンダー担当は @chikatoike さんです.
蛇足
本当は help のキーワード指定のコツなどの help を使いこなす tips を書こうと思っていたのですが,下記の場所に十分まとまった情報があったのでやめました. 参考までに.
:help help-context
→ help のキーワードの指定の仕方:help index
→ help 一覧- Hack #45: help を引く
- Hack #199: :helpに慣れ親しむ