Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nodeCache gets cleared with every edit in a file, resulting in slow didChange events on larger code bases #1595

Closed
folke opened this issue Sep 27, 2022 · 28 comments
Labels
bug Something isn't working enhancement New feature or request performance Related to the performance of the langauge server
Milestone

Comments

@folke
Copy link
Contributor

folke commented Sep 27, 2022

https://github.com/sumneko/lua-language-server/blob/d9fe11277e76caa2bf38292efe2df68ec1d43d10/script/vm/node.lua#L481

Right now, during a document/didChange event, files.onWatch("version") is called, which clears the whole nodeCache.

This means that every edit in a document clears the cache, which makes completion slow in a code base of about 2000 files.

If I remove the clearing, then everything is fast.

Is there a better way to clear the nodeCache partially?

@sumneko
Copy link
Collaborator

sumneko commented Sep 28, 2022

I can consider adding a setting that delays resetting the cache, sacrificing some correctness for performance.

@sumneko sumneko added the enhancement New feature or request label Sep 28, 2022
@sumneko sumneko added this to the 3.6.0 milestone Sep 28, 2022
sumneko added a commit that referenced this issue Sep 28, 2022
@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

I don't fully understand this part of the code, but can't we clear the nodeCache for every key where guide.get_uri(k) == uri?
Instead of invalidating the whole cache?

@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

So at the end of node.lua:

files.watch(function (ev, uri)
    if ev == 'version' then
        if ws.isReady(uri) then
          for source, node in pairs(vm.nodeCache) do
            local ok, source_uri = pcall(guide.getUri, source)
            if ok and source_uri == uri then
              log.debug("clearing nodeCache entry " .. source_uri)
              vm.nodeCache[source] = nil
            end
          end
        end
    end
end)

This change makes completion to return instantly, while still clearing anything in the cache related to the file that was edited.

@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

To give more context about my performance issue:

I have a workspace library loaded that contains the global vim.

In an empty file in my workspace, I start typing vim to get completion.

Editing the empty file, constantly clears the complete nodeCache, so everything reated to the global vim from the library needs to be rebuilt constantly, while those files never change. This makes complation in this case extremely slow. I mean over 10 seconds to get completion of vim.. And this happens after every edit.

@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

Added a PR #1603 that fixes this

@sumneko
Copy link
Collaborator

sumneko commented Sep 30, 2022

node may contains objects of another file, so it cannot be cleared according to the uri of node.
Global varaibles are compiled by another system in script/vm/global.lua, it will not be cleared until the file is edited.

Could you please provide a demo project about you performance issue, it may be caused by some other bugs.

@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

Some more details:

The problem seems to be caused by this types file https://github.com/folke/lua-dev.nvim/blob/main/types/vim.lua.

This file never actually gets required, but is just for the lua-language-server to know about the global vim.

https://github.com/folke/lua-dev.nvim/blob/main/types and the NEOVIM runtime are both added to the workspace libraries.

When worksapce libraries is small, there is no performance issue. Completion of vim. is always fast.

When I add a bunch of additional folders as workspace libraries (Neovim plugins), completion of vim. starts to become very slow.

Of course, all those plugins also use the global vim in a lot of places.

With all the plugins in the workspace library, but without the types file, things are also fast.

I can reduce the types file to just vim=require("vim.uri") and things are slow again.

@sumneko
Copy link
Collaborator

sumneko commented Sep 30, 2022

Did you find any warning in server log, like global-manager getFields cost/global-manager getGlobals cost/local-id getFields takes ?

sumneko added a commit that referenced this issue Sep 30, 2022
@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

@sumneko nope. I even have a git stash laying around that implements a global field cache and global global cache, but it honestly doesn't seem to make a difference.

not clearing the nodeCache does make a huge difference.

If you say globals are handled by vm.globals, does this mean clearing or not clearing the nodeCache should not make a difference in this case?

@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

Just ran with your extra logging, and still nothing. The globals script is not the issue I think.

It's the didChange events that take a long time.



[19:14:39.924][warn] [#0:script/proto/proto.lua:171]: Method [textDocument/didChange] takes [1.315]sec. {
  jsonrpc = "2.0",
  method = "textDocument/didChange",
  params = {
    contentChanges = { {
        range = {
          end = {
            character = 8,
            line = 14
          },
          start = {
            character = 8,
            line = 14
          }
        },
        rangeLength = 0,
        text = "."
      } },
    textDocument = {
      uri = "file:https:///home/folke/dot/config/nvim/lua/plugins/treesitter.lua",
      version = 16
    }
  }
}
[19:14:39.928][warn] [#0:script/provider/provider.lua:591]: Completion takes 1.318 sec.
[19:14:39.929][warn] [#0:script/proto/proto.lua:171]: Method [textDocument/completion] takes [1.319]sec. {
  id = 12,
  jsonrpc = "2.0",
  method = "textDocument/completion",
  params = {
    context = {
      triggerCharacter = ".",
      triggerKind = 2
    },
    position = {
      character = 9,
      line = 14
    },
    textDocument = {
      uri = "file:https:///home/folke/dot/config/nvim/lua/plugins/treesitter.lua"
    }
  }
}

@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

I assume the time for completion in this case didnt take 1.319, but about 0.004 seconds instead? The logging is wrong because of the async and completion is waiting till the didChange finished?

@sumneko
Copy link
Collaborator

sumneko commented Sep 30, 2022

Sorry, I need to catch the train tomorrow to take a holiday.
I will continue to check this issue after a week.

@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

@sumneko same thing here. Also leaving on a holiday weekend 😄

I'll continue debugging this next week and see if I can find something.

Have a great weekend!

@sumneko
Copy link
Collaborator

sumneko commented Sep 30, 2022

Ah, I mean 7 days ~

@folke
Copy link
Contributor Author

folke commented Sep 30, 2022

@sumneko no worries. Enjoy your holiday!

@sumneko sumneko added the performance Related to the performance of the langauge server label Sep 30, 2022
@sumneko
Copy link
Collaborator

sumneko commented Oct 9, 2022

Could you please provide a demo, I can try to debug it.

@folke
Copy link
Contributor Author

folke commented Oct 9, 2022

I found what caused the issue.

This file is just used for types and builds the global vim object.
https://github.com/folke/lua-dev.nvim/blob/a37b9ba11a4d1ed97284b416526e6251d23d58e1/types/vim.lua#L24

The slow-down is caused by this line
https://github.com/neovim/neovim/blob/edc8a1f04631b021f9c5e79f7162e34f7c3299db/runtime/lua/vim/shared.lua#L9

If I remove that line, everything is fast.

Not sure what a proper way would be to fix this.

@folke
Copy link
Contributor Author

folke commented Oct 9, 2022

They added that line to prevent some errors by luacheck.

In other files they do local vim = assert(vim), which causes a similar slow-down.

@folke
Copy link
Contributor Author

folke commented Oct 9, 2022

I just submitted a PR to fix this on the Neovim end: neovim/neovim#20551

@sumneko
Copy link
Collaborator

sumneko commented Oct 9, 2022

Thank you, I will further investigate why this writing method causes performance issues.

@sumneko
Copy link
Collaborator

sumneko commented Oct 11, 2022

Unfortunately, I can't reproduce this problem.
Can you provide a complete demo project?

@folke
Copy link
Contributor Author

folke commented Oct 11, 2022

I'm trying to assemble a demo project, but I can't reproduce it that way. I really don't get it.

this .luarc.json is slow when trying to complete `vim.api` or other
{
  "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
  "runtime": {
    "version": "LuaJIT",
    "path": [
      "lua/?.lua",
      "lua/?/init.lua"
    ],
    "pathStrict": false
  },
  "workspace": {
    "ignoreDir": [
      "plugins",
      "types",
      "runtime",
      "workspace"
    ],
    "library": [
      "/home/folke/.local/share/bob/v0.8.0/nvim-linux64/share/nvim/runtime",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/Comment.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/LuaSnip",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/SchemaStore.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/animation.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/cmp-buffer",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/cmp-cmdline",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/cmp-cmdline-history",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/cmp-emoji",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/cmp-nvim-lsp",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/cmp-nvim-lsp-signature-help",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/cmp-path",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/cmp_luasnip",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/dial.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/diffview.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/dressing.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/fidget.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/github-notifications.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/gitsigns.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/hlargs.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/hop.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/incline.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/indent-blankline.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/lualine.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/mason-lspconfig.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/mason.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/middleclass",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/mini.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/neo-tree.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/neogen",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/neogit",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/neorg",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/neoscroll.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nui.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/null-ls.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-autopairs",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-bufferline.lua",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-cmp",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-colorizer.lua",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-dap",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-dap-ui",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-lspconfig",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-navic",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-notify",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-scrollbar",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-spectre",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-surround",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-terminal.lua",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-toggleterm.lua",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-treehopper",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-treesitter",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-treesitter-refactor",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-treesitter-textobjects",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-treesitter-textsubjects",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-ts-context-commentstring",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/nvim-web-devicons",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/octo.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/one-small-step-for-vimkind",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/packer.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/playground",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/plenary.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/refactoring.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/rust-tools.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/specs.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/sqlite.lua",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/symbols-outline.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/telescope-file-browser.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/telescope-fzf-native.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/telescope-z.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/telescope.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/typescript.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/vim-illuminate",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/vim-matchup",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/vim-startuptime",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/windows.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/opt/yanky.nvim",
      "/home/folke/.local/share/nvim/site/pack/packer/start/dashboard-nvim",
      "/home/folke/projects/lua-dev.nvim",
      "/home/folke/projects/lua-dev.nvim/types/stable",
      "/home/folke/projects/neoconf.nvim",
      "/home/folke/projects/neoconf.nvim/types",
      "/home/folke/projects/noice.nvim",
      "/home/folke/projects/notifier.nvim",
      "/home/folke/projects/persistence.nvim",
      "/home/folke/projects/todo-comments.nvim",
      "/home/folke/projects/tokyonight.nvim",
      "/home/folke/projects/trouble.nvim",
      "/home/folke/projects/twilight.nvim",
      "/home/folke/projects/which-key.nvim",
      "/home/folke/projects/zen-mode.nvim"
    ]
  }
}
copy of the same .luarc.json, but pointing to directory workspace, where I copied all the directories
{
  "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
  "runtime": {
    "version": "LuaJIT",
    "path": [
      "lua/?.lua",
      "lua/?/init.lua"
    ],
    "pathStrict": false
  },
  "workspace": {
    "ignoreDir": [
      "plugins",
      "types",
      "runtime",
      "workspace"
    ],
    "library": [
      "workspace/animation.nvim",
      "workspace/cmp-buffer",
      "workspace/cmp-cmdline",
      "workspace/cmp-cmdline-history",
      "workspace/cmp-emoji",
      "workspace/cmp-nvim-lsp",
      "workspace/cmp-nvim-lsp-signature-help",
      "workspace/cmp-path",
      "workspace/cmp_luasnip",
      "workspace/Comment.nvim",
      "workspace/dashboard-nvim",
      "workspace/dial.nvim",
      "workspace/diffview.nvim",
      "workspace/dressing.nvim",
      "workspace/fidget.nvim",
      "workspace/github-notifications.nvim",
      "workspace/gitsigns.nvim",
      "workspace/hlargs.nvim",
      "workspace/hop.nvim",
      "workspace/incline.nvim",
      "workspace/indent-blankline.nvim",
      "workspace/lua-dev.nvim",
      "workspace/lualine.nvim",
      "workspace/LuaSnip",
      "workspace/mason-lspconfig.nvim",
      "workspace/mason.nvim",
      "workspace/middleclass",
      "workspace/mini.nvim",
      "workspace/neo-tree.nvim",
      "workspace/neoconf.nvim",
      "workspace/neogen",
      "workspace/neogit",
      "workspace/neorg",
      "workspace/neoscroll.nvim",
      "workspace/noice.nvim",
      "workspace/notifier.nvim",
      "workspace/nui.nvim",
      "workspace/null-ls.nvim",
      "workspace/nvim-autopairs",
      "workspace/nvim-bufferline.lua",
      "workspace/nvim-cmp",
      "workspace/nvim-colorizer.lua",
      "workspace/nvim-dap",
      "workspace/nvim-dap-ui",
      "workspace/nvim-lspconfig",
      "workspace/nvim-navic",
      "workspace/nvim-notify",
      "workspace/nvim-scrollbar",
      "workspace/nvim-spectre",
      "workspace/nvim-surround",
      "workspace/nvim-terminal.lua",
      "workspace/nvim-toggleterm.lua",
      "workspace/nvim-treehopper",
      "workspace/nvim-treesitter",
      "workspace/nvim-treesitter-refactor",
      "workspace/nvim-treesitter-textobjects",
      "workspace/nvim-treesitter-textsubjects",
      "workspace/nvim-ts-context-commentstring",
      "workspace/nvim-web-devicons",
      "workspace/octo.nvim",
      "workspace/one-small-step-for-vimkind",
      "workspace/packer.nvim",
      "workspace/persistence.nvim",
      "workspace/playground",
      "workspace/plenary.nvim",
      "workspace/refactoring.nvim",
      "workspace/runtime",
      "workspace/rust-tools.nvim",
      "workspace/SchemaStore.nvim",
      "workspace/specs.nvim",
      "workspace/sqlite.lua",
      "workspace/stable",
      "workspace/symbols-outline.nvim",
      "workspace/telescope-file-browser.nvim",
      "workspace/telescope-fzf-native.nvim",
      "workspace/telescope-z.nvim",
      "workspace/telescope.nvim",
      "workspace/todo-comments.nvim",
      "workspace/tokyonight.nvim",
      "workspace/trouble.nvim",
      "workspace/twilight.nvim",
      "workspace/types",
      "workspace/typescript.nvim",
      "workspace/vim-illuminate",
      "workspace/vim-matchup",
      "workspace/vim-startuptime",
      "workspace/which-key.nvim",
      "workspace/windows.nvim",
      "workspace/yanky.nvim",
      "workspace/zen-mode.nvim"
    ]
  }
}

The second one is from the demo project I'm creating for you to test. It has a subdirectory workspace and then a copy of every directory from the first file.

But what is super weird is that completion is instant in the demo project! I honestly don't understand how this can be the case. Same amount of files, same library. Only difference is the demo project has a copy of the original files. I'm a bit at loss here :)

I must be missing something obvious here.

Any ideas?

@folke
Copy link
Contributor Author

folke commented Oct 11, 2022

I'm able to reproduce it! Please see https://github.com/folke/lua-language-server/tree/perf_demo_project

If you open the file test.lua in the git root, and try to complete first vim. and then vim.api. you'll see that everything is fast.

If you open the file test/test.lua, in the test directory (so test as rootdir and test/.luarc.json as config file), then you'll see that it is all very slow.

Both .luarc.json files point to the same directories under /workspace. The one in the project root, uses workspace/..., the one under /test uses ../workspace/...

@sumneko
Copy link
Collaborator

sumneko commented Oct 12, 2022

It is caused by #1592
This PR leads to frequently scan all files (about 3,000) and cross-compare their file names.

@sumneko sumneko added the bug Something isn't working label Oct 12, 2022
@folke
Copy link
Contributor Author

folke commented Oct 12, 2022

I don't think it's related to #1592, since this happens with pathStrict=false, so that code path ran anyway.

When pathStrict=true, that code can be optimized though. Not needed to loop over all files, since we know what files to look for in the libraries.

@folke
Copy link
Contributor Author

folke commented Oct 12, 2022

Just to clarify. The following configurations have the same amount of files / workspace folders, but results are fast in the first one and slow in the second one:

  1. rootdir=/demo_project/ config=/demo_project/.luarc.json -> includes all folders in workspace in the library
  2. rootdir=/demo_project/test/ config=/demo_project/test/.luarc.json -> includes all folders in ../workspace in the library

@folke
Copy link
Contributor Author

folke commented Oct 12, 2022

@sumneko the PR prevents looping over the files when pathStrict=true.

In that case, completions are super fast now with both root dirs :)

@sumneko
Copy link
Collaborator

sumneko commented Oct 12, 2022

This problem has nothing to do with PR. Because your PR has changed the indentation, I mistakenly thought that these codes was all from PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request performance Related to the performance of the langauge server
Projects
None yet
Development

No branches or pull requests

2 participants