Performance Tuning
Neovim with many plugins can become slow if not carefully managed. This lesson shows how to profile and optimize.
Measure Startup Time
# Measure startup time from the command line
nvim --startuptime /tmp/startup.log && nvim +quit
cat /tmp/startup.log | sort -k2 -rn | head -20
" Inside Neovim with lazy.nvim
:Lazy profile
Lazy Loading (The Most Impactful Fix)
Every plugin that doesn't lazy-load increases startup time:
-- Bad: loads immediately
{
"heavy/plugin",
}
-- Good: loads only when needed
{
"heavy/plugin",
event = { "BufReadPost", "BufNewFile" }, -- on file open
-- or:
cmd = "HeavyCommand", -- on command
-- or:
ft = { "python" }, -- on filetype
-- or:
keys = { "<leader>xx" }, -- on keypress
}
Lazy Loading Priority
| Load trigger | When it loads |
|---|---|
| lazy = false | Immediately (only for critical plugins) |
| event = "VimEnter" | Right after Vim starts |
| event = "VeryLazy" | After everything else |
| event = "BufReadPost" | When opening a file |
| ft = "python" | Only for Python files |
| cmd = "Cmd" | Only when command is used |
| keys = { "..." } | Only when key is pressed |
Disable Unused Built-ins
performance = {
rtp = {
disabled_plugins = {
"gzip",
"matchit",
"matchparen",
"netrwPlugin", -- if using neo-tree
"tarPlugin",
"tohtml",
"tutor",
"zipPlugin",
},
},
},
Large File Optimizations
vim.api.nvim_create_autocmd("BufReadPre", {
callback = function(args)
local max_filesize = 500 * 1024 -- 500KB
local ok, stats = pcall(vim.loop.fs_stat, args.file)
if ok and stats and stats.size > max_filesize then
-- Disable heavy features for large files
vim.b.large_file = true
vim.opt_local.syntax = "off"
vim.opt_local.swapfile = false
vim.opt_local.foldmethod = "manual"
vim.opt_local.undolevels = -1
vim.cmd("LspStop")
vim.notify("Large file: disabling LSP, syntax, undo", vim.log.levels.WARN)
end
end,
})
Treesitter Performance
-- Disable for large files
highlight = {
enable = true,
disable = function(lang, buf)
local max_filesize = 100 * 1024 -- 100KB
local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf))
if ok and stats and stats.size > max_filesize then
return true
end
end,
},
Check Memory Usage
# Memory used by Neovim process
ps -o pid,rss,comm -p $(pgrep nvim)
# Inside Neovim
:lua print(collectgarbage("count") .. " KB Lua memory")
Profiling Lua Code
-- Profile a specific plugin config
local start = vim.loop.hrtime()
require("heavy.plugin").setup({})
local end_ = vim.loop.hrtime()
print(("Setup took %.2f ms"):format((end_ - start) / 1e6))
Quick Performance Checklist
- All plugins use
event,cmd,ft, orkeysfor lazy loading - Built-in unused plugins disabled in
lazy.setup() - LSP
updatetimeset to 300 - Large file autocmd disables heavy features
- Treesitter highlight disabled for large files
-
swapfile = false(avoids disk I/O) - Startup time measured with
:Lazy profile
In LazyVim
LazyVim is highly optimized out of the box and loads all plugins lazily by default, typically achieving a startup time of ~100-200ms even with dozens of plugins installed. The vim.loader.enable() experimental module is already enabled by default. Performance tuning in LazyVim is mostly about managing your extras.
To see exactly what is slowing down your startup in LazyVim, you can use the built-in profiler:
- Open Neovim
- Run
:Lazy profile - Sort by load time to identify bottlenecks
Often, startup slowdowns in LazyVim are caused by enabling too many heavy LazyExtras (like language servers you don't need) or adding poorly optimized custom plugins in lua/plugins/.
| Aspect | Vanilla Neovim | LazyVim |
|---|---|---|
| Plugin Loading | Manual configuration of event, cmd, etc. | Handled automatically by lazy.nvim and default configs |
| Bytecode Cache | Requires manual setup of vim.loader.enable() | Pre-configured and enabled automatically |
| Finding Bottlenecks | Manual profiling and timestamp tracking | Simply use :Lazy profile |
| Disabling Bloat | Commenting out plugin specs | Disabling unused features via :LazyExtras |