Skip to content

perf(#3386): skip getcwd per file in custom filter - #3387

Merged
alex-courtis merged 3 commits into
nvim-tree:masterfrom
hood:perf/3386-filters-cwd
Nov 7, 2026
Merged

alex-courtis merged 3 commits into
nvim-tree:masterfrom
hood:perf/3386-filters-cwd

Conversation

@hood

@hood hood commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Filters:custom calls vim.uv.cwd() for every scanned file, even with no custom filters.

This change caches cwd once per scan and introduces an early exit when the ignore list is empty, eliminating redundant system calls across large directories.

Now cwd is resolved in Filters:prepare as it's already the place for things computed once per directory (bufinfo, bookmarks). The cached cwd cannot drift or change between prepare and its subsequent use, as both ops execute during the same sync scan.

cwd is resolved only when pattern filters actually exist in ignore_list, avoiding unnecessary calls when pattern filtering is inactive.

Measured improvements:

  • Reload: 2,645 ms → 1,294 ms (~51% reduction) (!)
  • Expand all: 22.6 s → 16.7 s (~26% reduction)
  • Initial open: 786 ms → 492 ms (~37% reduction)

NOTE: I benchmarked on macOS running Neovim 0.13-dev against a large test repository containing ~15k nodes and ~3k Git status entries, still untested on Windows and Linux environments, as well as Neovim 0.10 and 0.11 releases.

AI usage: helped myself w/ Claude Code (Opus 5.5) while self-reviewing the code and measuring the improvements.

resolves #3386

@alex-courtis alex-courtis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love your work! ~70% reduction of Filters:custom execution

Similar testing as #3388

max_folder_discovery = 5000,

  filters = {
    custom = function(abs)
      return false
    end,

Added microsecond profiling to all exit points: local prof = log.profile_start("Filters:custom")

branch

: ; grep "END.*Filters:custom" $XDG_STATE_HOME/nvim/nvim-tree.log | sed -E 's/.* ([0-9]+)us/\1/g' | paste -sd+ | bc
100701
: ; grep "END.*Filters:custom" $XDG_STATE_HOME/nvim/nvim-tree.log | sed -E 's/.* ([0-9]+)us/\1/g' | paste -sd+ | bc
105111
: ; grep "END.*Filters:custom" $XDG_STATE_HOME/nvim/nvim-tree.log | sed -E 's/.* ([0-9]+)us/\1/g' | paste -sd+ | bc
102734

master

: ; grep "END.*Filters:custom" $XDG_STATE_HOME/nvim/nvim-tree.log | sed -E 's/.* ([0-9]+)us/\1/g' | paste -sd+ | bc
324099
: ; grep "END.*Filters:custom" $XDG_STATE_HOME/nvim/nvim-tree.log | sed -E 's/.* ([0-9]+)us/\1/g' | paste -sd+ | bc
302898
: ; grep "END.*Filters:custom" $XDG_STATE_HOME/nvim/nvim-tree.log | sed -E 's/.* ([0-9]+)us/\1/g' | paste -sd+ | bc
301922

Comment thread lua/nvim-tree/explorer/filters.lua Outdated
-- filter custom regexes
local relpath = utils.path_relative(path, vim.uv.cwd())
-- cwd is resolved once per directory by prepare as getcwd is slow on some platforms e.g. ~60us on macOS
local relpath = utils.path_relative(path, cwd or vim.uv.cwd())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need or vim.uv.cwd ? Is there a situation in which self.ignore_list is populated but cwd is not?

If not, could we add an early exit safety check at if next(self.ignore_list) == nil then ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aight, agreed. Dropped the fallback and added not cwd to the early exit. ignore_list is only set in the constructor and every should_filter* caller goes through prepare, so cwd is only nil when there are no patterns or getcwd fails. Just note that custom patterns are now skipped entirely. On master, basename patterns would still have matched. I know this is explicitly what you asked for, I'm just re-stating it here so we're all 100% in sync on this, at the cost of me being redundant 😄.

or self:buf(path, status.bufinfo)
or self:dotfile(path)
or self:custom(path)
or self:custom(path, status.cwd)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for following the odd patterns in this class. It is a bit of a mess.

@alex-courtis alex-courtis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love your work, such performance gains!

I've added you to the project so that you can push branches directly.

@alex-courtis
alex-courtis merged commit 7e1da84 into nvim-tree:master Nov 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: Filters:custom calls getcwd for every file, even with no custom filters

2 participants