dstein64 / nvim-scrollview

A Neovim plugin that displays interactive vertical scrollbars and signs.
MIT License
517 stars 9 forks source link

High CPU usage and degradation over time #101

Closed jemag closed 1 year ago

jemag commented 1 year ago

First of all thanks for the great plugin, been using it for years.

Description

It seems that with the latest versions and with the addition of Signs, CPU usage is significant and keeps increasing over time.

Running nvim-scrollview with

require("scrollview").setup({
  -- excluded_filetypes = {'nerdtree'},
  current_only = false,
  winblend = 50,
  base = "right",
  signs_on_startup = {
    "conflicts",
    "cursor",
    "diagnostics",
    "marks",
    "search",
    "spell",
    "textwidth",
    "trail",
  },
})

results in significant initial CPU usage (16.3%): scrollview-signs-initial-cpu

The problem is that the CPU usage will also increase over time. Here it is after 10 min at idle (35.2%): scrollview-signs-10min-cpu

and after 20 min (45.6%): scrollview-signs-20min-cpu

Without any signs, aka:

require("scrollview").setup({
  -- excluded_filetypes = {'nerdtree'},
  current_only = false,
  winblend = 50,
  base = "right",
  signs_on_startup = {
  },
})

The CPU usage remains relatively stable over time. Here it is without signs at 15min (11.7%): scrollview-no-signs-15min-cpu

Comparatively, here is the same nvim configuration with nvim-scrollview uninstalled after 24 hours (1.3%): scrollview-uninstalled-24h CPU usage here is low and does not increase over time.


It took me a while to find out that the problem was coming from nvim-scrollview. The initial sign was after profiling Neovim using profile.nvim and obtaining the following profile: profile.zip Which can be analyzed using chrome://tracing or https://ui.perfetto.dev/

Using the profile, we can see that even for a short amount of time of Neovim being open: image get_sign_eligible_windows is called quite a bit and for significant duration.

Additional Info

nvim version: NVIM v0.10.0-dev-552+g08db61b19 nvim-scrollview version: 251c5b9

dstein64 commented 1 year ago

Thanks for the detailed analysis and reporting the issue @jemag.

I saw that you mentioned "Here it is after 10 min at idle". Can you provide more details on what's meant by idle? My interpretation is that you're seeing high CPU usage even when not actively interacting with Neovim.

I would expect higher CPU usage while scrolling, but wouldn't expect more (or any) CPU usage while the editor is not actively being used. I ran experiments and confirmed that CPU usage is higher than without nvim-scrollview when scrolling a large document (holding <ctrl-d>). However, I see 0.0% CPU usage when the editor is open but not being used. If you're seeing increased usage even without actively using the editor, do you have any ideas how I could reproduce the issue?

jemag commented 1 year ago

Can you provide more details on what's meant by idle? My interpretation is that you're seeing high CPU usage even when not actively interacting with Neovim.

Yes I am indeed only opening Neovim and then not touching it for all those amount of time. So Neovim is just sitting there in a Tmux pane while I check the CPU increase over time.

However, I see 0.0% CPU usage when the editor is open but not being used. If you're seeing increased usage even without actively using the editor, do you have any ideas how I could reproduce the issue?

I can try and see if I can build a more minimal config reproduction, although it might be a bit hard given that I most likely need at least LSP and diagnostics running.

Perhaps there is a wrong interaction with something in my config, but the CPU usage definitely comes from nvim-scrollview. I also tried similar alternatives such as https://github.com/lewis6991/satellite.nvim, and CPU usage was fine there (tested over 24h).

dstein64 commented 1 year ago

One thing that might be causing an issue is if 'updatetime' is set low (nvim-scrollview refreshes signs and scrollbars on CursorHold event). Do you have a low value for updatetime? The default is 4000.

set updatetime?

Update: CursorHold won't repeatedly fire when the editor is inactive, so that likely isn't the issue.

Another possibility is that another plugin is modifying a buffer and the TextChanged event is firing, which would cause scrollbar and sign refreshing.

If you run the following commands, is an increasing value continuously printed while the editor is not actively being used?

let x = 0
autocmd TextChanged * :let x += 1 | echo x

What if you try, after restarting Neovim for each attempt, with some of the other events that would cause scrollview refreshing?

let x = 0
autocmd WinEnter * :let x += 1 | echo x
let x = 0
autocmd TermEnter * :let x += 1 | echo x

... (also CmdwinLeave, WinScrolled, WinResized, BufWinEnter, TabEnter, VimResized, OptionSet)

let x = 0
autocmd CursorHold * :let x += 1 | echo x

Update: It might also be DiagnosticChanged event, since that would also cause a scrollview refresh if the diagnostics signs are enabled.

jemag commented 1 year ago

Thank you for helping me debug this, I have tried all the events and it seems that OptionSet is indeed continuously triggering, here is a video showing it: https://github.com/dstein64/nvim-scrollview/assets/7985687/335b5dad-75ac-4478-a9dd-8a8a11d67153 Not quite sure what could be the source of it, printing the autocmd callback payloads, I get mostly these:

{
  buf = 0,
  event = "OptionSet",
  file = "",
  id = 36,
  match = "eventignore"
}
{
  buf = 0,
  event = "OptionSet",
  file = "",
  id = 36,
  match = "modified"
}

with eventignore at around double the rate of modified

dstein64 commented 1 year ago

I just realized that nvim-scrollview only refreshes if the OptionSet event triggers for the winbar option.

So the test would be:

let x = 0
autocmd OptionSet winbar :let x += 1 | echo x

Presumably that wouldn't trigger since eventignore and modified are the primary options being set. If you disable nvim-scrollview, are those options continuously being set, or does it only happen when scrollview is enabled?

jemag commented 1 year ago

Not quite sure if I understood properly but using

let x = 0
autocmd OptionSet winbar :let x += 1 | echo x

with nvim-scrollview running the number does not increase.

Using

let x = 0
autocmd OptionSet * :let x += 1 | echo x

with nvim-scrollview running, the number increases.

Using it again with nvim-scrollview uninstalled, the number does NOT increase. Basically OptionSet is not being continuously triggered if nvim-scrollview is not installed.

dstein64 commented 1 year ago

I guess that the OptionSet event firings you're seeing are due to the scrollbars continuously being refreshed, since the plugin modifies options over the course of refreshing plugins. I think that neither of those options would be triggering the refreshing, but are rather a side effect of refreshing occurring.

If you comment out the following lines (which would effectively disable the plugin), does the problem go away? https://github.com/dstein64/nvim-scrollview/blob/251c5b9b15f7937bfe9a24bf3066a9944936c32d/lua/scrollview.lua#L1724-L1768 Is one of the lines the culprit, causing the problem when adding back?

dstein64 commented 1 year ago

I think your most recent comment confirms that the OptionSet events are a side effect of scrollview refreshing, as opposed to being the cause itself of the refreshing.

jemag commented 1 year ago

yes using

let x = 0
autocmd OptionSet * :let x += 1 | echo x

with the plugin with lines 1724-1768 commented out, results in the number staying the same (OptionSet not constantly triggered).

dstein64 commented 1 year ago

With those lines commented out, does the CPU usage remain low? When removing the comments one at a time, is there a specific line that makes the CPU usage go back up?

jemag commented 1 year ago

so far CPU seems to remain low with all lines commented out. I have to go out for now, but I'll test removing comments one at a time when I come back.

jemag commented 1 year ago

Tested all of them and no significant baseline CPU or CPU increase over time until I uncommented the CursorHold one.

Once I uncommented autocmd CursorHold * :lua require('scrollview').refresh_bars_async() then the OptionSet autocmd counter started increasing again on its own, even when I am not focusing the Neovim pane. CPU now increases over time as before.

dstein64 commented 1 year ago

What is your updatetime set to?

set updatetime?
jemag commented 1 year ago

100

I could change it although that would impact other behaviors and plugins relying on CursorHold as well.

I wouldn't mind if there is some baseline CPU usage, but shouldn't the CPU still not increase over time, no matter the updatetime?

dstein64 commented 1 year ago

I can reproduce the issue with set updatetime=100. The CursorHold event fires repetitively, causing scrollbars to keep being updated. It looks like the reason for this is because scrollview uses :normal! during the course of its processing, and this triggers CursorHold to fire, which then causes scrollview to refresh, which causes CursorHold, ..., and this keeps repeating.

I've removed automatic refreshing for CursorHold in f07e8ddbb2c9fca324f2bdc6a9703b98ab0e2f39. Thanks for your help running the experiments to debug.

"I wouldn't mind if there is some baseline CPU usage, but shouldn't the CPU still not increase over time, no matter the updatetime?"

I was able to reproduce that prior to f07e8ddbb2c9fca324f2bdc6a9703b98ab0e2f39. The issue is concerning since as you said, CPU usage should not increase. However, after removing the CursorHold autocmd, I don't see CPU usage increase over time, even when scrollview is refreshed every 10 milliseconds.

" The following example assumes a window with a scrollbar is open.
func MyHandler(timer)
  ScrollViewRefresh
endfunc
let timer = timer_start(10, 'MyHandler', {'repeat': -1})
jemag commented 1 year ago

Thanks for your help throughout all of this. Updated with the last commit and works great for me.