🛸

Neovimの設定を見直して起動を30倍速にした

2021/11/30に公開

https://twitter.com/KawarimiDoll/status/1465279959606255619

古い設定が非常に遅かったというだけの話なのですが…。

Neovimは、起動時に--startuptime <filename>オプションをつけると、起動ログをファイルに残すことができます。

❯ nvim --startuptime ./startup.log

最終行のNVIM STARTEDが起動時間です。

Before(たしか9月半ばくらい)

startup.log
789.996  000.002: --- NVIM STARTED ---

After(11月末)

startup.log
025.411  000.030: --- NVIM STARTED ---

こんな感じで高速化することができたので、やったことを書いておきます。

基本的にはプラグインまわりの見直しです。

高速化の手順

上記のstartupログおよび以下の記事で紹介されているプロファイルの結果を見て、時間のかかっているものを重点的に潰していきました。
https://vim-jp.org/vim-users-jp/2009/11/07/Hack-99.html

標準プラグインの無効化

startupログを見るとわかるのですが、neovimはユーザーが設定していない標準プラグインも結構読み込んでいます。この読み込みを防止します。
startupログで表示されているファイルを開く(gfが使えます)と、冒頭に以下のような条件文がある事がわかります。

nvim/runtime/plugin/man.vim(抜粋)
if exists('g:loaded_man')
  finish
endif
let g:loaded_man = 1

よって、このg:loaded_xxxという変数を設定ファイル内で定義することで、標準プラグインの読み込みをスキップできます。

init.vim
let g:did_install_default_menus = 1
let g:did_install_syntax_menu   = 1
let g:did_indent_on             = 1
let g:did_load_filetypes        = 1
let g:did_load_ftplugin         = 1
let g:loaded_2html_plugin       = 1
let g:loaded_gzip               = 1
let g:loaded_man                = 1
let g:loaded_matchit            = 1
let g:loaded_matchparen         = 1
let g:loaded_netrwPlugin        = 1
let g:loaded_remote_plugins     = 1
let g:loaded_shada_plugin       = 1
let g:loaded_spellfile_plugin   = 1
let g:loaded_tarPlugin          = 1
let g:loaded_tutor_mode_plugin  = 1
let g:loaded_zipPlugin          = 1
let g:skip_loading_mswin        = 1

did_install_default_menus did_install_syntax_menu skip_loading_mswinの3つはstartupログからはたどり着けませんが、設定しておくと不要な機能の読み込みをスキップしてくれます(参考にしたページを忘れてしまいました…)。

実際には各ファイルは一度開かれる(つまり、startupログのsourcing ...自体は消えない)のですが、冒頭の条件分岐によってすぐ閉じられるため、読み込み時間は大幅に短縮されます。

※ commentstringが設定されない事があるっぽい?のでftpluginはスキップしないほうが良いかもしれません…。上記を設定して不具合が生じた場合は適宜調整してください。

使用するプラグインの見直し

単純に、使っていないのに読み込んでいるプラグインが多くあったので削除しました。

高速化のためにプラグインを見直す中で、lua製のプラグインに置き換えたものがいくつかあります。
https://zenn.dev/hituzi_no_sippo/articles/871c06cdbc45b53181e3

また、一部のプラグインは以下の記事で紹介したmini.nvimに(カラースキームも含め)一本化することができました。
https://zenn.dev/kawarimidoll/articles/56d61ecbab9755

記事執筆時点で使用しているのは下記の26個です。

プラグイン(辞書順) 概要
bufpreview.vim denopsプラグイン。ブラウザでMarkdownのプレビューを行う。
capture.vim EXコマンドの出力をバッファに書き出すことができる。
coc.nvim LSP。Node.jsに依存している。
denops.vim Denoでプラグインを書くエコシステム。筆者は現状bufpreviewを使うためだけに入れている。
filetype.nvim 標準機能より高速にファイル形式を判別できる。
fzf Go製Fuzzy Finder。fzf-previewを使うために読み込んでいる。
gina.vim Git操作。筆者は直接呼び出すことはなく、fzf-previewを介して使っている。
gitsigns.nvim Git表示。編集箇所の表示、blameの表示、ステージングなどを行える。
hop.nvim easymotionのlua実装。特定の文字を狙ってジャンプすることができる。
impatient.nvim lua製プラグインの読み込み支援。
lazygit.nvim lazygitをfloating windowで操作できる。
mini.nvim 便利機能詰め合わせセット。
nvim-colorizer.lua 色指定文字列をその色でハイライトする。
nvim-hlslens 検索時にヒット数をvirtual textで表示する。
nvim-web-devicons deviconsの表示に使う。statuslineでアイコンを表示するために導入。
open-browser.vim URL文字列をブラウザで開く。
plenary.nvim luaプラグイン用ユーティリティ。gitsigns.nvimなどが依存している。
vim-asterisk *による検索を強化する。
vim-caser snake_case camelCase などの変換を提供する。
vim-expand-region Visualモードで選択範囲を良い感じに拡縮する。
vim-readme-viewer プラグインのREADMEを開く。Neovim用プラグインにはhelpがないものが散見されるため役立つ。
vim-showmarks Markを可視化する。
vim-silicon siliconを使い、コードを画像化する。
vimdoc-ja 日本語ドキュメント。あくまでNeovimではなくVimのドキュメントである点は注意が必要。
which-key.nvim キーマップを表示する。
winresizer Windowのサイズを簡単に調整できるようにする。

一部のプラグインは、Zennの解説記事を参考に導入しました。
https://zenn.dev/yano/articles/vim_plugin_top_10
https://zenn.dev/kouta/articles/ab2d9df961238e
https://zenn.dev/kato_k/articles/3aa7217a1636ca

プラグインの遅延読み込み

筆者はプラグインマネージャとしてvim-plugを使用しています。
vim-plugの遅延読み込み機能を使うと、プラグインを実際に使用するタイミングまで読み込まないことができるので、起動を高速化できます。

コマンドによる遅延読み込み

vim-plugの標準機能です。
特定のコマンドまたは<Plug>が実行されたときに読み込みます。
注意点として、プラグインが読み込まれない以上、デフォルトマッピングの設定もされません。自分でマッピングを定義する必要があります。

以下のような形で設定します。

init.vim
Plug '4513ECHO/vim-readme-viewer', { 'on': 'PlugReadme' }
Plug 'jacquesbh/vim-showmarks', { 'on': 'DoShowMarks' }
Plug 'kat0h/bufpreview.vim', { 'on': 'PreviewMarkdown' }
Plug 'kdheepak/lazygit.nvim', { 'on': 'LazyGit' }
Plug 'lambdalisue/gina.vim', { 'on': 'Gina' }
Plug 'segeljakt/vim-silicon', { 'on': 'Silicon' }
Plug 'simeji/winresizer', { 'on': 'WinResizerStartResize' }
Plug 'terryma/vim-expand-region', { 'on': '<Plug>(expand_region_' }
Plug 'tyru/capture.vim', { 'on': 'Capture' }
Plug 'tyru/open-browser.vim', { 'on': ['OpenBrowser', '<Plug>(openbrowser-'] }

必要なキーマッピングは自分で行います。

init.vim
nmap gx <Plug>(openbrowser-smart-search)
xmap gx <Plug>(openbrowser-smart-search)
xmap v <Plug>(expand_region_expand)
xmap <C-v> <Plug>(expand_region_shrink)

タイマー機能による遅延読み込み

これを使ったら何でもアリという気もするのですが、Vim起動時には読み込まず、起動から少し経過したところで読み込みを行います。
前述のコマンドによる遅延読み込みとは違い、自動で起動しておきたいものや、デフォルトのマッピングを使いたいものはこちらのほうが適しています。

https://qiita.com/Alice_ecilA/items/d251a90e4a71d67444dd#vimのタイマー機能で遅延読み込み

vim-plugの宣言部では、次のように{ 'on': [] }オプションを付け、読み込まないようにします。

init.vim
Plug 'arthurxavierx/vim-caser', { 'on': [] }
Plug 'folke/which-key.nvim', { 'on': [] }
Plug 'haya14busa/vim-asterisk', { 'on': [] }
Plug 'junegunn/fzf', { 'on': [], 'do': { -> fzf#install() } }
Plug 'kevinhwang91/nvim-hlslens', { 'on': [] }
Plug 'kyazdani42/nvim-web-devicons', { 'on': [] }
Plug 'lewis6991/gitsigns.nvim', { 'on': [] }
Plug 'neoclide/coc.nvim', { 'on': [], 'branch': 'release' }
Plug 'norcalli/nvim-colorizer.lua', { 'on': [] }
Plug 'nvim-lua/plenary.nvim', { 'on': [] }
Plug 'phaazon/hop.nvim', { 'on': [] }
Plug 'vim-denops/denops.vim', { 'on': [] }

そして、以下の関数s:LazyLoadPlugs内でplug#load()を使って読み込みます。

  • plug#load()がバッファを読み直すため、Zマークを使ってカーソル位置を保存しています。
  • lua系プラグインで初期設定が必要なものは、この関数の中に記述しています。
init.vim
function! s:LazyLoadPlugs(timer) abort
  " save current position by marking Z because plug#load reloads current buffer
  normal! mZ
  call plug#load(
        \   'denops.vim',
        \   'coc.nvim',
        \   'fzf',
        \   'gitsigns.nvim',
        \   'nvim-colorizer.lua',
        \   'nvim-hlslens',
        \   'nvim-web-devicons',
        \   'plenary.nvim',
        \   'hop.nvim',
        \   'vim-asterisk',
        \   'vim-caser',
        \   'which-key.nvim',
        \ )
  normal! g`Z
  delmarks Z

lua << EOF
  require('which-key').setup()
  require('colorizer').setup()
  require('hop').setup()
  require('hlslens').setup({ calm_down = true })
  require('gitsigns').setup({
    signs = {
      add          = { text = '+' },
      change       = { text = '~' },
      delete       = { text = '_' },
      topdelete    = { text = '‾' },
      changedelete = { text = '~_' },
    },
    current_line_blame = true,
  })
EOF
endfunction

call timer_start(20, function("s:LazyLoadPlugs"))

上記コードブロックの最後の行、call timer_start(20, function("s:LazyLoadPlugs"))により、Neovim起動20ms後に読み込みが走ります。
したがって、起動直後から完全機能で使えるわけではないのですが、筆者の操作速度では20msの差は問題になりませんでした。
これにより、ファーストビューはかなりの高速化が期待できます。

遅延読み込みしていないもの

以下のプラグインはstartupログに出力がなかったため、特に遅延読み込みを行っていません。

init.vim
Plug 'echasnovski/mini.nvim'
Plug 'lewis6991/impatient.nvim'
Plug 'nathom/filetype.nvim'
Plug 'vim-jp/vimdoc-ja'

おわりに

プラグインの読み込み設定を見直し、かなり起動を高速化できました。
タイマー遅延を使えるので、今後プラグインの増減があっても大幅に遅くなることはない想定です。

これまで起動速度を気にしていなかったという人は、一度計測してみると、思わぬボトルネックが見つかるかもしれません。
ご参考になれば幸いです。

おまけ 筆者のVim遍歴(ざっくり)

2020年前半まで
当時の勤め先のメンバーに合わせSublime textを主に使用
sshしたサーバー内で設定ファイルを書き換えるためにviを使うことを教わるがうまく操作できないことに不満を感じる

2020年6月ごろ
少しずつVimの勉強を始める
「実践Vim」を買う
dotfilesリポジトリを作り、vimrcを作成
その後少しずつプラグインなどを足していく
lightlineにaleを表示
カラースキームはgruvbox
vim-lspを導入するも使いこなせず

2021年春ごろ
Neovimに乗り換え
coc.nvimを使い始める
カラースキームはsonokai
vim-jp slackに参入

その後
Zennに記事を書いたりDeno Deployで遊んだりしている中でNeovim設定も育つ

2021年秋
vim-jpで起動速度や遅延読み込みの話題が出ていたことで自分が全然速度を気にしていなかったことを自覚

本記事に至る

Discussion