Open Gee19 opened 3 years ago
Thanks for pointing this out. I think I might know what's going on, but I'll have to look into it more closely.
Is there anything I can do to help? I really miss this plugin :(
Sorry, I completely forgot about this. Can you point me to a file that has the performance problem? Also, do you have the FastFold plugin installed?
No worries. I'll try and post a reproducible example at some point this week (hopefully today after work). I do have FastFold installed.
Looks like my initial thought that it was related to Pyright or an LSP server was incorrect. I was able to reproduce this with a minimal vimrc and the flask repo using neovim 0.5 stable and vim 8.2.
VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Apr 27 2021 18:57:19)
Included patches: 1-2815
NVIM v0.5.0
Build type: RelWithDebInfo
LuaJIT 2.1.0-beta3
.vimrc
call plug#begin('~/.vim/plugged')
Plug 'Konfekt/FastFold'
Plug 'kalekundert/vim-coiled-snake'
call plug#end()
git clone https://github.com/pallets/flask && cd flask
vim src/flask/helpers.py
hold the 'x' key on some random imports
The editor slows to a halt and accepts no input. I hope this helps and thanks for your time
Awesome, thanks for the minimal example. Sorry it's taken me so long to look into this, but I'll try to get to it soon.
I can reproduce the problem, although I haven't made any progress towards fixing it yet. It does seem that, for whatever reason, nvim
does not suffer from this performance issue. So that could be a work-around, if you can easily use nvim
.
@kalekundert what neovim version are you using? I saw it on 0.5 stable but I'm completely open to using a specific version to avoid this issue (I really like this plugin 😂)
I also have nvim==0.5.0. Not sure what the difference is. In any case, there is a real problem that needs to be fixed. This is related to #30: the problem goes away if I remove the code I added to fix that bug (which basically forces the folds to be recalculated every time the text changes).
I'm also getting the impression that coiledsnake is somehow preventing FastFold from working correctly. Specifically, I notice that :set foldmethod?
seems to always return expr
, while my understanding is that FastFold should keep it set to manual
most of the time.
Could debouncing the calculation help? Probably not ideal though
@kalekundert I've been using the changes from that PR for a day or two and haven't noticed any bugs and it completely fixed the performance issue. I'm not going to pretend I understand the repercussions of this change but it seems promising
That's good to hear, I'll have to take a close look at that PR. I'm not sure I understand the repercussions either, unfortunately...
I'm using neovim, and have been encountering slowdown issues with this plugin too.
I'm using neovim, and have been encountering slowdown issues with this plugin too.
Have you tried the changes in the PR mentioned in this issue? You could also try it out by installing my fork: https://github.com/Gee19/vim-coiled-snake
I've been using it since October 2021 with no issues but haven't tested it extensively.
I have to admit now that I can't reproduce the slowdown with either branch. I probably should have taken better notes on my testing method before removing the plugin (and commenting :grimacing:). I'll re-install this version of vim-coiled-snake
, and if I see a slowdown again, I'll compare with your branch. Apologies for the inconvenience!
I've started experiencing slow-downs again (on this version, not the fork), and did some profiling.
To test things, I ran the commands listed at the top of the file:
:profile start profile.log
:profile func *
:profile file *
Then I opened a new line with o<esc>
, then undid the operation with u
a few times (sorry, I didn't count). I'm afraid I can't share the file, but if it helps, it's a 2861-line pytest test file.
I'm not especially familiar with the profile file format, but here's the bits of the file that looked important (click to expand):
``` FUNCTION coiledsnake#RefreshFolds() Defined: ~/personal/dotfiles/vim/plugged/vim-coiled-snake/autoload/coiledsnake.vim:38 Called 11 times Total time: 8.241507 Self time: 0.080745 count total (s) self (s) 11 0.000034 let b:coiled_snake_marks = {} 11 4.046652 0.000124 let lines = s:LinesFromBuffer() 11 4.130163 0.027628 let folds = s:FoldsFromLines(lines) " Cache where each fold should open and close. 1177 0.040233 0.028533 for lnum in sort(keys(folds), 's:LowToHigh') 1166 0.001275 let l:fold = folds[lnum] " Open a fold at the appropriate level on this line. 1166 0.002718 let b:coiled_snake_marks[l:fold.lnum] = '>' . l:fold.level " If no inside line was found, the fold reaches the end of the file and " doesn't need to be closed. 1166 0.001201 if l:fold.inside_line == {} 22 0.000014 continue " If this fold and the next are the same type and separated only by " blank lines, allow a certain number of those lines to be included in " the fold. 1144 0.003704 elseif l:fold.type == get(l:fold.next_fold, 'type', '') && l:fold.level == get(l:fold.next_fold, 'level') 847 0.002271 let closing_lnum = min([ l:fold.inside_line.lnum + l:fold.num_blanks_below, l:fold.outside_line.lnum - 1]) " If only an inside line was specified, close the fold exactly on that " line. 297 0.000097 else 297 0.000339 let closing_lnum = l:fold.inside_line.lnum 1144 0.000312 endif " Indicate that the fold should be closed, but don't overwrite any " previous entries. Due to the way the code is organized, any previous " entries will be higher level folds, and we want those to take " precedence. 1144 0.001649 if ! has_key(b:coiled_snake_marks, closing_lnum) 1089 0.002357 let b:coiled_snake_marks[closing_lnum] = '<' . l:fold.level 1144 0.000330 endif " Ignore folds that end up opening and closing on the same line. 1144 0.000888 if closing_lnum <= l:fold.lnum unlet b:coiled_snake_marks[closing_lnum] 1144 0.000279 endif 1155 0.000400 endfor 11 0.000017 return b:coiled_snake_marks ```
```
FUNCTION
```
FUNCTION
I then repeated the experiment with the @Gee19's fork, and these are the (pretty similar looking) results:
``` FUNCTION coiledsnake#RefreshFolds() Defined: ~/personal/dotfiles/vim/plugged/vim-coiled-snake/autoload/coiledsnake.vim:37 Called 8 times Total time: 7.322363 Self time: 0.058180 count total (s) self (s) 8 0.000040 let b:coiled_snake_marks = {} 8 3.774136 0.000109 let lines = s:LinesFromBuffer() 8 3.499767 0.019186 let folds = s:FoldsFromLines(lines) " Cache where each fold should open and close. 856 0.025754 0.016178 for lnum in sort(keys(folds), 's:LowToHigh') 848 0.001201 let l:fold = folds[lnum] " Open a fold at the appropriate level on this line. 848 0.002498 let b:coiled_snake_marks[l:fold.lnum] = '>' . l:fold.level " If no inside line was found, the fold reaches the end of the file and " doesn't need to be closed. 848 0.001229 if l:fold.inside_line == {} 16 0.000012 continue " If this fold and the next are the same type and separated only by " blank lines, allow a certain number of those lines to be included in " the fold. 832 0.003639 elseif l:fold.type == get(l:fold.next_fold, 'type', '') && l:fold.level == get(l:fold.next_fold, 'level') 616 0.002174 let closing_lnum = min([ l:fold.inside_line.lnum + l:fold.num_blanks_below, l:fold.outside_line.lnum - 1]) " If only an inside line was specified, close the fold exactly on that " line. 216 0.000089 else 216 0.000305 let closing_lnum = l:fold.inside_line.lnum 832 0.000278 endif " Indicate that the fold should be closed, but don't overwrite any " previous entries. Due to the way the code is organized, any previous " entries will be higher level folds, and we want those to take " precedence. 832 0.001537 if ! has_key(b:coiled_snake_marks, closing_lnum) 792 0.002134 let b:coiled_snake_marks[closing_lnum] = '<' . l:fold.level 832 0.000297 endif " Ignore folds that end up opening and closing on the same line. 832 0.000824 if closing_lnum <= l:fold.lnum unlet b:coiled_snake_marks[closing_lnum] 832 0.000242 endif 840 0.000370 endfor 8 0.000023 return b:coiled_snake_marks ```
```
FUNCTION
```
FUNCTION
To my untrained eye, it looks as though 132_InitLineString
and 132_InitFold
being called lots of times is likely related to why so much time is spent in RefreshFolds
.
On closer inspection, it seems that the regex line matching in a loop is consuming a lot of the time. I wonder if there's a way the pattern matching could be sped up, or the results of calls cached?
I found a similar slowdown in NeoVim, with and without FastFold installed. Does anyone know of a anything that works? vim-coiled-snake
does folding better than I can find elsewhere, but it makes neovim too slow.
I think the way to tackle the speed issue of this plugin (on large files) would be to try to speed up the comparisons made by these regexes: https://github.com/kalekundert/vim-coiled-snake/blob/ce50abca6c3533cb9bf48e910f87ae91f2969740/autoload/coiledsnake.vim#L2-L17.
Perhaps some of these comparisons could be done faster? Maybe there's a way to avoid re-running them so often?
I worked on a Python neovim plugin that parses indented text (not Python code). It builds a tree of objects out of the parsed (indented) text. If I remember right, it was easy to subscribe to buffer changes, and the subscribing function receives line numbers any time buffer text changes, and it's then quite easy to only update the branch of the tree that has actually changed.
I wouldn't want to try to recreate the same thing in vimscript, though.
I noticed some weird performance issues when repeating a simple action in normal mode like
x
when in larger python repos. I initially thought this was a pyright issue but after profiling with the commands below it seems to be related to this plugin.I grepped the profile.log for functions that were called more than 1000 times, included 3 lines above and below each match for more info on the offending plugin, and total time spent for each.
This seems odd as I am not interacting with folds at all while this is occuring, just deleting some characters. Let me know if I can help debug any further.