Skip to content

Speed up loading file statuses in Git commit dialog by batching events and skipping events for up-to-date files#9324

Merged
mbien merged 1 commit intoapache:masterfrom
OndroMih:ondromih-git-commit-dlg-optimization-skip-no-update
Apr 10, 2026
Merged

Speed up loading file statuses in Git commit dialog by batching events and skipping events for up-to-date files#9324
mbien merged 1 commit intoapache:masterfrom
OndroMih:ondromih-git-commit-dlg-optimization-skip-no-update

Conversation

@OndroMih
Copy link
Copy Markdown
Contributor

@OndroMih OndroMih commented Apr 4, 2026

Skip firing events for up-to-date

Skip firing events for up-to-date files that are not
yet in the cache, since UPTODATE is the default for managed files.
This drastically reduces the time spent in the refreshStatusesBatch method on big repositories executed when Commit dialog opens. For example, on the Netbeans repository, from around 8 seconds to 20ms.

Batch status change notifications

Batch status change notifications to avoid per-file event overhead.

Replace per-file PROP_FILE_STATUS_CHANGED firing in refreshStatusesBatch
with a single PROP_FILES_STATUS_CHANGED batch event, eliminating 100k
redundant propertyChange/schedule/SwingUtilities.invokeLater calls on
first load. This improves performance a bit because it eliminates many method calls.
However, in the end, the number of files changed is the same so the event handler
still needs to process all of them.

Move status updates to a background thread

File status update requires I/O operation to refresh files metadata from FS. This is very slow when many files need to be updated. Moving them to a background thread offloads this slow operation from the main thread.
Updates can run asynchrnously without blocking the main thread that fires the status events, they just update UI hints, they have no impact no behavior.
For the whole Netbeans repository, this shortens the time it takes to complete the firePropertyChange event from 6 seconds to 1 second.

Complements #9304, which speeds up the process even more, in a different area.


Click to collapse/expand PR instructions

PR approval and merge checklist:

  1. Was this PR correctly labeled, did the right tests run? When did they run?
  2. Is this PR squashed?
  3. Are author name / email address correct? Are co-authors correctly listed? Do the commit messages need updates?
  4. Does the PR title and description still fit after the Nth iteration? Is the description sufficient to appear in the release notes?

If this PR targets the delivery branch: don't merge. (full wiki article)

@mbien mbien added git [ci] enable versioning job ci:dev-build [ci] produce a dev-build zip artifact (7 days expiration, see link on workflow summary page) labels Apr 4, 2026
@apache apache locked and limited conversation to collaborators Apr 4, 2026
@apache apache unlocked this conversation Apr 4, 2026
Copy link
Copy Markdown
Member

@mbien mbien left a comment

Choose a reason for hiding this comment

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

this looks good to me. Thanks a lot for the improvements.

left some comments inline but its nothing important

@OndroMih OndroMih force-pushed the ondromih-git-commit-dlg-optimization-skip-no-update branch from 94bdb4a to e48d9d6 Compare April 7, 2026 11:24
@mbien
Copy link
Copy Markdown
Member

mbien commented Apr 7, 2026

@OndroMih for some reason I see an extra commit in this PR branch.

git pull --rebase --autostash git@github.com:apache/netbeans master
should hopefully resolve it.

@OndroMih
Copy link
Copy Markdown
Contributor Author

OndroMih commented Apr 7, 2026

@mbien , I implemented the changes you requested, please review again.

I added one more optimization - property change listeners run asynchronously - they both do a slow I/O operation for each file and running them asynchronously unblocks the main thread. This doesn't have impact on functionality because the final stage of updating the statuses was already asynchronous and it only updates the icons of files to reflect they status.

Here's a sceenshot from profile before the change - the firePropertyChange event took 6 seconds:
image

After these changes, when I forced update also on noupdate files, this decreased to 1 second:
image

When I put back skipping update on noupdate files, the firePropertyChange method is rarely called at all. And if it is, then only with a few files that are not marked with noupdate.

@OndroMih OndroMih force-pushed the ondromih-git-commit-dlg-optimization-skip-no-update branch from e48d9d6 to 2f76482 Compare April 7, 2026 12:17
@OndroMih
Copy link
Copy Markdown
Contributor Author

OndroMih commented Apr 7, 2026

@mbien

@OndroMih for some reason I see an extra commit in this PR branch.

Thanks, I did it and it seems it helped.

@mbien mbien self-requested a review April 7, 2026 12:46
## Skip firing events for up-to-date 

Skip firing events for up-to-date files that are not
yet in the cache, since UPTODATE is the default for managed files. 
This drastically reduces the time spent in the refreshStatusesBatch method on big repositories executed when Commit dialog opens. For example, on the Netbeans repository, from around 8 seconds to 20ms.

## Batch status change notifications

Batch status change notifications to avoid per-file event overhead.

Replace per-file PROP_FILE_STATUS_CHANGED firing in refreshStatusesBatch
with a single PROP_FILES_STATUS_CHANGED batch event, eliminating 100k
redundant propertyChange/schedule/SwingUtilities.invokeLater calls on
first load. This improves performance a bit because it eliminates many method calls. 
However, in the end, the number of files changed is the same so the event handler 
still needs to process all of them.

## Move status updates to a background thread

File status update requires I/O operation to refresh files metadata from FS. This is very slow when many files need to be updated. Moving them to a background thread offloads this slow operation from the main thread.
Updates can run asynchronously without blocking the main thread that fires the status events, they just update UI hints, they have no impact no behavior.
For the whole Netbeans repository, this shortens the time it takes to complete the firePropertyChange event from 6 seconds to 1 second.
@OndroMih OndroMih force-pushed the ondromih-git-commit-dlg-optimization-skip-no-update branch from 2f76482 to 642c71b Compare April 7, 2026 13:17
@mbien
Copy link
Copy Markdown
Member

mbien commented Apr 10, 2026

the changes here do make sense to me, but I didn't really see a difference when i tested, see below.

This drastically reduces the time spent in the refreshStatusesBatch method on big repositories executed when Commit dialog opens. For example, on the Netbeans repository, from around 8 seconds to 20ms.

how do you open the commit dialog (e.g right click on ?? -> commit) and how many files do you have in a modified state? I wasn't able to reproduce those kinds of performance gains but maybe the context of the commit dialog was different. What OS are we testing on?

I noticed a few other things while testing but that would be for future PRs.

Details

e.g: the method is called 11 times when a single file is modified and saved - and the actual refresh task runs 3 times. The original intention was likely to debounce events at that point, so that the task runs once. Its also unclear why it has to look at so many files when one file updates. (but this might be a side effect of the nbm ant project, haven't tested with maven)

modified and saved InvalidException.java

refreshAnnotations (remove=true): [.../netbeans/platform/o.n.bootstrap/src/org/netbeans/InvalidException.java]
fireFileStatusChangedTask:
 - filesToRefresh {[org.netbeans.modules.masterfs.ui.FileBasedFSWithUI@4889d62d]=[.../netbeans/platform/o.n.bootstrap/src/org/netbeans/InvalidException.java@95bc86a5:44e06a1c]}
 - parentsToRefresh {[org.netbeans.modules.masterfs.ui.FileBasedFSWithUI@4889d62d]=[...@f86303a2:60617141, /home@2d5901f:3656c194, .../netbeans/platform/o.n.bootstrap@466893f1:18e4d027, .../netbeans@8b9cd233:23a5c4b0, /@12d5be:5d549a0, /home/mbien@4d8498cd:e67c9d, .../netbeans/platform@d503edd1:64f0f89c, .../netbeans/platform/o.n.bootstrap/src@5955e424:3b937fad, .../netbeans/platform/o.n.bootstrap/src/org/netbeans@94b441ba:17377ee8, .../netbeans/platform/o.n.bootstrap/src/org@1a468e1b:2c977558]}
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap/src/org/netbeans/InvalidException.java]
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap/src/org/netbeans/InvalidException.java]
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap/src/org/netbeans/InvalidException.java]
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap/src/org/netbeans]
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap]
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap/build.xml, .../netbeans/platform/o.n.bootstrap/apichanges.xml, .../netbeans/platform/o.n.bootstrap/arch.xml, .../netbeans/platform/o.n.bootstrap/readme, .../netbeans/platform/o.n.bootstrap/build, .../netbeans/platform/o.n.bootstrap/src, .../netbeans/platform/o.n.bootstrap/launcher, .../netbeans/platform/o.n.bootstrap/nbproject, .../netbeans/platform/o.n.bootstrap/manifest.mf, .../netbeans/platform/o.n.bootstrap/external, .../netbeans/platform/o.n.bootstrap/test]
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap/src]
fireFileStatusChangedTask:
 - filesToRefresh {[org.netbeans.modules.masterfs.ui.FileBasedFSWithUI@4889d62d]=[.../netbeans/platform/o.n.bootstrap/launcher@f7fc83fe:25a36361, .../netbeans/platform/o.n.bootstrap@466893f1:18e4d027, .../netbeans/platform/o.n.bootstrap/build.xml@da95fe39:44268f23, .../netbeans/platform/o.n.bootstrap/nbproject@95566867:3f315454, .../netbeans/platform/o.n.bootstrap/apichanges.xml@63901630:3dc06607, .../netbeans/platform/o.n.bootstrap/external@df5a7f0b:b863e0c, .../netbeans/platform/o.n.bootstrap/build@23415c6e:5bf9a25, .../netbeans/platform/o.n.bootstrap/src/org/netbeans/InvalidException.java@95bc86a5:44e06a1c, .../netbeans/platform/o.n.bootstrap/arch.xml@e57560df:45b27aae, .../netbeans/platform/o.n.bootstrap/src@5955e424:3b937fad, .../netbeans/platform/o.n.bootstrap/test@cf8d9f70:3741353f, .../netbeans/platform/o.n.bootstrap/src/org/netbeans@94b441ba:17377ee8, .../netbeans/platform/o.n.bootstrap/readme@6194ddac:339590ea, .../netbeans/platform/o.n.bootstrap/manifest.mf@1a85e918:1e84cc9b]}
 - parentsToRefresh {[org.netbeans.modules.masterfs.ui.FileBasedFSWithUI@4889d62d]=[]}
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap/src/org/netbeans]
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap/build.xml, .../netbeans/platform/o.n.bootstrap/apichanges.xml, .../netbeans/platform/o.n.bootstrap/arch.xml, .../netbeans/platform/o.n.bootstrap/readme, .../netbeans/platform/o.n.bootstrap/build, .../netbeans/platform/o.n.bootstrap/src, .../netbeans/platform/o.n.bootstrap/launcher, .../netbeans/platform/o.n.bootstrap/nbproject, .../netbeans/platform/o.n.bootstrap/manifest.mf, .../netbeans/platform/o.n.bootstrap/external, .../netbeans/platform/o.n.bootstrap/test]
refreshAnnotations (remove=false): [.../netbeans/platform/o.n.bootstrap/src]
fireFileStatusChangedTask:
 - filesToRefresh {[org.netbeans.modules.masterfs.ui.FileBasedFSWithUI@4889d62d]=[.../netbeans/platform/o.n.bootstrap/launcher@f7fc83fe:25a36361, .../netbeans/platform/o.n.bootstrap/build.xml@da95fe39:44268f23, .../netbeans/platform/o.n.bootstrap/nbproject@95566867:3f315454, .../netbeans/platform/o.n.bootstrap/apichanges.xml@63901630:3dc06607, .../netbeans/platform/o.n.bootstrap/build@23415c6e:5bf9a25, .../netbeans/platform/o.n.bootstrap/external@df5a7f0b:b863e0c, .../netbeans/platform/o.n.bootstrap/arch.xml@e57560df:45b27aae, .../netbeans/platform/o.n.bootstrap/src@5955e424:3b937fad, .../netbeans/platform/o.n.bootstrap/test@cf8d9f70:3741353f, .../netbeans/platform/o.n.bootstrap/src/org/netbeans@94b441ba:17377ee8, .../netbeans/platform/o.n.bootstrap/readme@6194ddac:339590ea, .../netbeans/platform/o.n.bootstrap/manifest.mf@1a85e918:1e84cc9b]}
 - parentsToRefresh {[org.netbeans.modules.masterfs.ui.FileBasedFSWithUI@4889d62d]=[]}

@OndroMih
Copy link
Copy Markdown
Contributor Author

OndroMih commented Apr 10, 2026

@mbien , I always test by clicking on the "Commit All - Repository..." button in the toolbar. Or by selecting the root repository directory in the Favorites view and then "Team -> Commit..." in the menu. This invokes the Commit dialog for the whole repository, not only for selected subdirectory or a single selected file. This is the way how I always commit, to avoid skipping some files that I modified outside of the selected project that I forgot about.

When doing commit per the whole Netbeans repository, the Commit dialog refreshes the whole file cache, going through all the files in the directory of the repository and its subfolders.

When I do "Team -> Commit..." on when a single file is selected, or on a single project, e.g. on the Git Client Library project, the Commit dialog loads fast even without these changes.

@OndroMih
Copy link
Copy Markdown
Contributor Author

Fixing the whole logic of the refreshAnnotations annotations method and how many times it gets called was beyond my focus, which was only to speed up the Commit dialog. I didn't look at optimizing in other scenarios, e.g. when a single file is edited and saved.

@mbien
Copy link
Copy Markdown
Member

mbien commented Apr 10, 2026

always test by clicking on the "Commit All - Repository..." button in the toolbar.

thanks. its what I suspected but i wanted to be sure that we test the same thing ;) (the button isn't on the git toolbar by default).

freshly cloned netbeans repo and modified a single java file.

running Commit All - Repository:

  • NB 29: ~12s
  • PR: ~6s

until the commit button enables. (JDK 26; linux)

Fixing the whole logic of the refreshAnnotations annotations method and how many times it gets called was beyond my focus

yes I know that. I was just inspecting call sites to ensure that this change has no side effects.

@mbien mbien added this to the NB30 milestone Apr 10, 2026
@OndroMih
Copy link
Copy Markdown
Contributor Author

Thanks for testing it too, @mbien . My other PR #9304 reduces this time even more, to around 2 seconds I think when combined with this PR.

@mbien
Copy link
Copy Markdown
Member

mbien commented Apr 10, 2026

merging. thanks for the improvements!

@mbien mbien merged commit 2b92f87 into apache:master Apr 10, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:dev-build [ci] produce a dev-build zip artifact (7 days expiration, see link on workflow summary page) git [ci] enable versioning job performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants