Closed renantmagalhaes closed 5 months ago
Lsp configurations are loaded on startup, on my machine it adds around ~30ms which I find tolerable. So 2-3 seconds are definitely unusual.
nvim --startuptime
and see what takes the extra time.@dundalek One thing I forgot to mention: I'm running this on WSL2 with Opensuse Tumbleweed as distribution. For WSL, I do not limit my CPU(16 threads), and I allocate 6GB of RAM.
When testing your config I replaced my lsp / mason / cmp config with the snippet I put in my original post
I will later test on my bare metal server to see if the behavior is the same, and verify if the issue is with WSL ... or maybe even Tumbleweed.
I will let you know. Thanks!
@renantmagalhaes are there any differences in load time if you open something which requires different LSPs? Or is it just by loading plugins?
I have a few guesses what might cause this.
:LspInfo
to see which are loaded and use the exclude LSP option to turn some of them of and see if there's any differencenix-shell -p lua_ls
(replace lua_ls with one you commonly use) and see how long time that takes. For me, this is always a slow action, even if the nixpkgs is already cached, in the order of a second or two. I don't think this will make you any wiser since this is supposed to happen concurrently, but maybe one of LSPs somehow fails in a way that locks the main thread? nix-shell -p lsp1 lsp2 ... lspn
and then open nvim. Any difference?nvim .
. Something with loading a plugin might be the issue. Might not be a lazy-lsp problem but zero-lsp.This should give a better idea on where the issue lies.
@dundalek as a side note, have you looked into using flakes to cache the nix-shells? This might significantly speed up the loading of LSPs. Maybe as an option in setup? Another cool idea would be to provide a way for the user to provide shell.nix files associated to filetypes and use these instead of a simple nix-shell for these files, or maybe just use a shell.nix at project root if there is any. What do you think about this?
Thanks for the tips @JoakimPaulsson
I still think --startuptime
output would likely provide most info for understanding, but good to have other things to try out. Good point that if neovim is started with opened files (or perhaps with some kind of session restoration plugin) that would indeed trigger starting the lsp servers. Although I would be very surprised if lsp in neovim is implemented in a way that it would block.
@dundalek as a side note, have you looked into using flakes to cache the nix-shells? This might significantly speed up the loading of LSPs. Maybe as an option in setup? Another cool idea would be to provide a way for the user to provide shell.nix files associated to filetypes and use these instead of a simple nix-shell for these files, or maybe just use a shell.nix at project root if there is any. What do you think about this?
I am aware of Flakes, but still waiting for them to get out of experimental phase and not familiar with them very much. I created https://github.com/dundalek/lazy-lsp.nvim/issues/33 for discussion, feel free to add any thoughts/notes.
I recently got a windows' laptop for work and can confirm the 2/3s of startup time with lazy.lsp (on wsl). I use the same config as my linux's setup, where lazy.lsp only adds ~30ms of load.
This startup time is observable with or without file. Opening a file, a directory or a newfile does the same thing.
Doing nvim newfile
still takes 2/3s.
Here is a startuptime log:
We can see the 2012.010 opening bufers
vs 54.624 opening buffers
without lazy.lsp (logs without lazy.lsp for reference):
I wonder if this could be solved by simply calling lspconfig.setup
lazily? Instead of relying on lspconfig to start known lsps, we could use a bufenter autocommand to setup the lsps of the newly opened filetype.
Thanks for the log, very helpful (made an edit adding expandable detail blocks to make scrolling easier).
What I see is that loading lsp configs takes 50ms (not that bad compared to 30ms on Linux).
The main culprit comes from lspconfig.manager
which runs afterwards and takes 1900ms (compared to 20ms on my Linux machine). I see that in the lspconfig setup
code there are some async scheduled calls to manager
, but it is not clear what could be the issue.
I am not familiar how to profile Lua to dig in what the manager spends the time on.
However, I can imagine a possibility that maybe only one or few configs cause the misbehavior (e.g. ending up scanning a lot of files to figure out workspace directory or something like that). This hypothesis could be confirmed/disproved by bisecting the servers as follows:
excluded_servers
, this should lead to fast startup again.@zoriya Would you be up to trying that? Then we could add them to curated servers and exlude by default as a workaround.
I wonder if this could be solved by simply calling
lspconfig.setup
lazily? Instead of relying on lspconfig to start known lsps, we could use a bufenter autocommand to setup the lsps of the newly opened filetype.
I thought about loading the configs lazily in the past, but shaving a few tens of ms were not motivating enough. Might re-consider due to this issue. The big question mark is that "simply calling lazily" could potentially get tricky. It would need digging deeper into how lspconfig works and which events to hook into. I opened #34 with more details but not sure if/when I will get to that.
I like your hypothesis, I'll try bisecting servers tomorrow.
I just tried bisecting but a single server does not seems to be the cause of this slowness. Disabling every server result in a good startup time:
But with every server I enable, the startup time gets slower and slower. For example, opening buffer
takes between ~20 and ~40ms more by server I enable.
Here is a startup log with 4 servers enabled:
Great findings! I think we found the cause, if each server setup takes ~20ms then running ~100 of them will add up to that 2s slowdown.
Btw I just found other report about slow lspconfig on WSL (matches the observations): https://github.com/neovim/nvim-lspconfig/issues/3015
So calling the setup functions lazily as we talked about seems like the only viable fix at this point. I will try to take a look, but no promises.
I pushed an update to lazily setup servers in https://github.com/dundalek/lazy-lsp.nvim/commit/72f954b946bfb591c4f482a3251b1112803a03ad which should hopefully address this issue.
Currently it is behind a flag, use the experimental_lazy_setup = true
option to enable it. I plan to test drive it for a bit and if I don't encounter issues I will enable it by default.
Can confirm that it works like a charm! Startup time has gone down a lot (most of it seems to be due to others plugins that don't work well with wsl), servers do start when I open a new ft without any issue.
Thanks a lot!
PS: Here is my startuptime for reference (668.561 067.314: opening buffers
for the important line!)
Now that lsp can be lazy loaded, I think it would be nice to allow configs to be functions. This would allow lsp specific plugins to be lazy loaded.
For example:
configs = {
jsonls = {
settings = {
json = function() return {
schemas = require('schemastore').json.schemas(),
validate = { enable = true },
} end,
},
},
}
Awesome to hear!
I still see ~120ms from lspconfig.server_configurations.*
(which is larger on WSL than usual). That can be shaved off as a part of #34.
I wonder if on_new_config
callback would work to make it lazy, something like:
jsonls = {
on_new_config = function(config)
config.settings = {
json = {
schemas = require('schemastore').json.schemas(),
validate = { enable = true },
},
}
end,
},
It does work for jsonls (turn out, LazyVim does that too) but it does not work for custom handlers so it does not work for omnisharp for example:
omnisharp = {
handlers = {
["textDocument/definition"] = require('omnisharp_extended').definition_handler,
["textDocument/references"] = require('omnisharp_extended').references_handler,
["textDocument/implementation"] = require('omnisharp_extended').implementation_handler,
},
...
},
omnisharp was slow because it required telescope eagerly, I fixed this behavior in https://github.com/Hoffs/omnisharp-extended-lsp.nvim/pull/32, and now it loads in 0.21ms (according to Lazy's profiler). It might not be worth the complexity to lazy load that.
omnisharp was slow because it required telescope eagerly, I fixed this behavior in Hoffs/omnisharp-extended-lsp.nvim#32, and now it loads in 0.21ms (according to Lazy's profiler). It might not be worth the complexity to lazy load that.
Sounds good, let's leave it for now, but open a new issue if you encounter a case with a larger potential gains.
The lazy loading seems to work well for me, so I will push an update soon to enable it by default. I want to add some unit tests before that to make sure and will close the issue when done.
There's any reason that would explain the increase launch time of neovim when using this plugin?
My usual start time is around 30ms (with mason+lsp+cmp). If I use the default configuration in this project's README.md, two things happen.
Details on how to solve this problem are in the help page. Execute the following command
:help lsp-zero-guide:fix-extend-lspconfig Press ENTER or type command to continue
return { { "dundalek/lazy-lsp.nvim", dependencies = { "neovim/nvim-lspconfig", { "VonHeikemen/lsp-zero.nvim", branch = "v3.x" }, "hrsh7th/cmp-nvim-lsp", "hrsh7th/nvim-cmp", }, config = function() local lsp_zero = require("lsp-zero")
}, }