Skip to content

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

Open
OndroMih wants to merge 1 commit intoapache:masterfrom
OndroMih:ondromih-git-commit-dlg-optimization-skip-no-update
Open

Speed up loading file statuses in Git commit dialog by batching events and skipping events for up-to-date files#9324
OndroMih wants to merge 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

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.

Also 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.

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)

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.

Also 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.
@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

Comment on lines -582 to +589
for (ChangedEvent event : events) {
fireFileStatusChanged(event);
if (!events.isEmpty()) {
listenerSupport.firePropertyChange(PROP_FILES_STATUS_CHANGED, null, events);
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.

good catch. aggregating events can certainly reduce overhead under load. Reminds me a little on #8955 where I saw 1.5mil event handler invocations when a large project group opened.

fireStatusChanged(changedEvent.getFile());
} else if (event.getPropertyName().equals(FileStatusCache.PROP_FILES_STATUS_CHANGED)) {
List<FileStatusCache.ChangedEvent> changedEvents = (List<FileStatusCache.ChangedEvent>) event.getNewValue();
Set<File> files = new HashSet<>(changedEvents.size());
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.

this is super nitpicky. but the constructor arg isn't for the element count, it is to size the internal table - which sometimes leads to initial sizes which are too small.

if you want you can bump the module to javac.release=21 (see project.properties) and then use the HashSet.newHashSet(numElements) factory which does some math before setting the size of the internal table.

feel free to update the commit and force push in place.

Comment on lines +1138 to +1140
for (FileStatusCache.ChangedEvent changedEvent : changedEvents) {
if (revisionLeft == Revision.HEAD // remove when we're able to refresh single file changes for Local vs. any revision
&& revisionRight == Revision.LOCAL && affectsView(changedEvent)) {
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.

could the revisionLeft/Right check be moved before the loop?

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