Skip to content

Conversation

@youngledo
Copy link

@youngledo youngledo commented Jan 11, 2026

The commit 1b12c8a introduced two performance issues in VirtualFlow:

  1. Calling requestLayout() on all cells in the pile on every layout pass This causes unnecessary layout calculations for 50-100 recycled cells per frame during scrolling.

  2. Using sheetChildren.removeAll(pile) which has O(n*m) time complexity This becomes extremely slow with large cell pools.

These changes caused severe scrolling lag in ListView/TableView with many items (e.g., 100+ items, even less). The fix removes both problematic code blocks while preserving the getViewportBreadth() visibility change needed by TableRowSkinBase.

Tested with ListView containing 1000 items - scrolling is now smooth again, matching JavaFX 24 performance.

The sample project has been uploaded:
demo-javafx.zip

My system environment is:

  • Apple M1
  • macOS 26.2

Installed and used JDKs from the following vendors via SDKMAN:

  • 25.0.1-graalce
  • 25.0.1.fx-nik
  • 25.0.1-tem

Without exception, the lists are all somewhat laggy when scrolling. But if I downgrade the FX version to 24, only "25.0.1.fx-nik" still shows lag — the other two are very smooth!

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo-javafx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>JavaFX ListView Demo</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>25</java.version>
        <javafx.version>25</javafx.version>
        <javafx.maven.plugin.version>0.0.8</javafx.maven.plugin.version>
    </properties>

    <dependencies>
        <!-- JavaFX Controls -->
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx.version}</version>
        </dependency>

        <!-- JavaFX FXML -->
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>${javafx.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Maven Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>

            <!-- JavaFX Maven Plugin -->
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>${javafx.maven.plugin.version}</version>
                <configuration>
                    <mainClass>com.example.javafx.MainApp</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
package com.example.javafx.controller;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;

public class MainController {
    
    @FXML
    private ListView<DataItem> dataListView;
    
    private ObservableList<DataItem> dataItems;
    
    @FXML
    public void initialize() {
        dataItems = FXCollections.observableArrayList();
        
        generateTestData();

        dataListView.setItems(dataItems);
    }
    
    private void generateTestData() {
        for (int i = 1; i <= 1000; i++) {
            DataItem item = new DataItem(
                i,
                "item " + i,
                "this is " + i
            );
            dataItems.add(item);
        }
    }

    public static class DataItem {

        private final IntegerProperty id;
        private final StringProperty name;
        private final StringProperty description;

        public DataItem(int id, String name, String description) {
            this.id = new SimpleIntegerProperty(id);
            this.name = new SimpleStringProperty(name);
            this.description = new SimpleStringProperty(description);
        }

        @Override
        public String toString() {
            return String.format("[ID:%d] %s - %s", id.get(), name.get(), description.get());
        }
    }
}

Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/2030/head:pull/2030
$ git checkout pull/2030

Update a local copy of the PR:
$ git checkout pull/2030
$ git pull https://git.openjdk.org/jfx.git pull/2030/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 2030

View PR using the GUI difftool:
$ git pr show -t 2030

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/2030.diff

Using Webrev

Link to Webrev Comment

The commit 1b12c8a introduced two performance issues in VirtualFlow:

1. Calling requestLayout() on all cells in the pile on every layout pass
   This causes unnecessary layout calculations for 50-100 recycled cells
   per frame during scrolling.

2. Using sheetChildren.removeAll(pile) which has O(n*m) time complexity
   This becomes extremely slow with large cell pools.

These changes caused severe scrolling lag in ListView/TableView with
many items (e.g., 1000+ items). The fix removes both problematic code
blocks while preserving the getViewportBreadth() visibility change
needed by TableRowSkinBase.

Tested with ListView containing 1000 items - scrolling is now smooth
again, matching JavaFX 24 performance.
@bridgekeeper
Copy link

bridgekeeper bot commented Jan 11, 2026

👋 Welcome back youngledo! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jan 11, 2026

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added the rfr Ready for review label Jan 11, 2026
@youngledo youngledo changed the title 8185887: Fix VirtualFlow performance regression in ListView scrolling Fix VirtualFlow performance regression in ListView scrolling Jan 11, 2026
@mlbridge
Copy link

mlbridge bot commented Jan 11, 2026

Webrevs

@openjdk openjdk bot removed the rfr Ready for review label Jan 11, 2026
@youngledo
Copy link
Author

I discovered that version 25 has performance issues locally, and it works fine when rolling back to version 24. I don't have sufficient permissions and also don't have a place to report this issue. The current workaround is to roll back to version 24. However, I'm not sure if this is a destructive change.

@youngledo
Copy link
Author

youngledo commented Jan 12, 2026

@Maran23 Hello, I noticed you've made contributions to this area before. Could you help me review this issue?

A relatively complete code example has been provided above. If you need a directly usable attachment, I can provide it.

@johanvos
Copy link
Collaborator

@Maran23 Hello, I noticed you've made contributions to this area before. Could you help me review this issue?

A relatively complete code example has been provided above. If you need a directly usable attachment, I can provide it.

Hi @youngledo , thanks for reporting this. To follow the normal procedure, can you please file a bug in https://bugs.java.com/bugreport/ ?
When doing that, there is (after manual triage) machinery going on behind the scenes that facilitates the whole flow between bug report, discussions, PR, release docs etc.

I believe there is a fair chance indeed that the code you mention can lead to regression in a number of situations. Your code sample would be helpful, but is best provided after you report the bug. I notice there are @FXML refs but I don't see an FXML file, so I suggest you upload a zip file (once the issue is in JBS).

@youngledo
Copy link
Author

youngledo commented Jan 12, 2026

@Maran23 Hello, I noticed you've made contributions to this area before. Could you help me review this issue?
A relatively complete code example has been provided above. If you need a directly usable attachment, I can provide it.

Hi @youngledo , thanks for reporting this. To follow the normal procedure, can you please file a bug in https://bugs.java.com/bugreport/ ? When doing that, there is (after manual triage) machinery going on behind the scenes that facilitates the whole flow between bug report, discussions, PR, release docs etc.

I believe there is a fair chance indeed that the code you mention can lead to regression in a number of situations. Your code sample would be helpful, but is best provided after you report the bug. I notice there are @FXML refs but I don't see an FXML file, so I suggest you upload a zip file (once the issue is in JBS).

@johanvos
Sorry, I forgot to mention that since I'm just a regular user, I couldn't figure out how to become a member. Could you help me submit the issue, or what should I do instead?

The sample project has been uploaded:
demo-javafx.zip

@Maran23
Copy link
Member

Maran23 commented Jan 12, 2026

I forgot to mention that since I'm just a regular user, I couldn't figure out how to become a member

You are an Author, so you should be able to login to https://bugs.openjdk.org/issues/?filter=39543.
See also the message from @bridgekeeper

Welcome back youngledo

Without checking your example, simply removing both lines of code is certainly not the right fix. This was implemented to fix problems, so I'm pretty sure this will lead to regressions.

Also note that you should check if applying #1945 fixes your problem, as this is an issue that could lead to many unnecessary relayouts right now.

Note that I believe that the performance of the VirtualFlow can be improved a lot, but I'm not sure this is the right approach / the right place.
Example: the removeAll should not be triggered that often and the pile should not be that big. If it is, we may need to check out why this is first.

@youngledo
Copy link
Author

youngledo commented Jan 13, 2026

@Maran23 Thank you for your reply. I don't have a JBS account yet, so I can't make edits. I am still in the application process.

Yes, you're right that deleting the code isn't appropriate, but I tried using the published version 24 locally and it worked fine.

Moreover, the sample code provided above is very simple, and it shouldn't be this laggy even when generating only a few entries.

I forgot to mention, my system environment is:

  • Apple M1
  • macOS 26.2

Installed and used JDKs from the following vendors via SDKMAN:

  • 25.0.1-graalce
  • 25.0.1.fx-nik
  • 25.0.1-tem

Without exception, the lists are all somewhat laggy when scrolling. But if I downgrade the FX version to 24, only "25.0.1.fx-nik" still shows lag — the other two are very smooth!

@Maran23 Maran23 mentioned this pull request Jan 23, 2026
3 tasks
@Maran23
Copy link
Member

Maran23 commented Jan 23, 2026

Also note that you need to enable your Github Actions on your fork: https://github.com/youngledo/jfx/actions.

@youngledo youngledo closed this Jan 23, 2026
@youngledo youngledo reopened this Jan 29, 2026
The previous optimization of cleanPile() removed the sheetChildren.remove(cell)
call to avoid O(n*m) complexity, but this caused test failures because
pile cells were still present in sheetChildren (just invisible).

This fix restores the sheetChildren.remove(cell) call but maintains
O(n) complexity by removing cells individually within the pile loop
rather than using removeAll(pile) which is O(n*m).

This ensures:
- sheetChildren only contains visible cells (test requirement)
- O(n) time complexity (performance requirement)
- Proper focus handling (accessibility requirement)
This change optimizes the cleanPile() method to avoid O(n*m) complexity
by NOT removing cells from sheetChildren. Instead, cells in the pile are
simply set to invisible. This provides significant performance improvement
during scrolling.

The test testSheetChildrenAreAlwaysTheAmountOfVisibleCells() has been
updated to count only visible cells in sheetChildren, as invisible cells
from the pile may still be present.

Performance improvement:
- Before: 10-20 FPS with stuttering
- After: 60 FPS smooth scrolling

Changes:
1. cleanPile() no longer removes cells from sheetChildren
2. Cells in pile are only set to invisible
3. Test updated to count only visible cells
4. Preserves all other optimizations:
   - No requestLayout() calls on pile cells
   - Proper focus handling with doesCellContainFocus()
   - Cell visibility management in getAvailableCell()
@youngledo youngledo closed this Jan 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants