7

I'm stumped. I have a use-case which involves the rendering of arbitrarily large, paginated tabular result sets in TableView form.

What I'm finding is that both fine scrolling (via touch gestures or mouse wheel on the TableView) and coarse scrolling (via dragging of scroll bar slider) degrade to unuseability as I scroll to larger row ordinals.

The degradation happens at relatively small row counts (< ~1M). Note that the data model only ever holds a small subset of data at any given row offset and loads pages dynamically in response to scroll events.

I've distilled the use case to a few lines of code below. I'm been able to support a similar such use case in the past (about 4 years ago, 1billion rows) with no issue, so I know this is doable. Whatever the issue is now, however, I just can't see it.

Desperate for help...

Incidentally, I've tried this with various permutations of SDK and JavaFX with same effect.

package com.example.demo;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableListBase;
import javafx.scene.CacheHint;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import java.util.ArrayList;
import java.util.List;

public class Main extends Application {

    public static void main(String[] args) {
        launch();
    }

    @Override
    public void start(Stage stage) {
        final TableView<Row> table = new TableView();
        final BorderPane borderPane = new BorderPane();
        final Scene scene = new Scene(borderPane, 800, 600);

        TableColumn<Row, String> col = new TableColumn<>("row");
        col.setCellValueFactory(p -> new SimpleStringProperty(p.getValue().data[0].toString()));
        col.setPrefWidth(100);
        table.getColumns().add(col);

        col = new TableColumn<>("A");
        col.setCellValueFactory(p -> new SimpleStringProperty(p.getValue().data[1].toString()));
        col.setPrefWidth(100);
        table.getColumns().add(col);

        col = new TableColumn<>("B");
        col.setCellValueFactory(p -> new SimpleStringProperty(p.getValue().data[2].toString()));
        col.setPrefWidth(100);
        table.getColumns().add(col);

        col = new TableColumn<>("C");
        col.setCellValueFactory(p -> new SimpleStringProperty(p.getValue().data[3].toString()));
        col.setPrefWidth(100);
        table.getColumns().add(col);

        table.setCache(true);
        table.setEditable(false);
        table.setCacheHint(CacheHint.SPEED);

        table.setItems(getModel(10_000_000));

        borderPane.setCenter(table);
        stage.setScene(scene);

        stage.show();
    }

    private static ObservableList<Row> getModel(final int rowCount) {
        final List<Row> data = new ArrayList<>(rowCount);
        for (int x = 0; x < rowCount; x++) {
            data.add(new Row(x, new Object[] {x, "a_" + x, "b_" + x, "c_" + x }));
        }
        return FXCollections.unmodifiableObservableList(FXCollections.observableList(data));
    }

    private static final class Row {
        public final int rowOrdinal;
        public final Object[] data;

        public Row(final int rowOrdinal, final Object[] data) {
            this.rowOrdinal = rowOrdinal;
            this.data = data;
        }

        @Override
        public boolean equals(Object o) {
            return o != null && o.getClass() == Row.class && ((Row)o).rowOrdinal == rowOrdinal;
        }
        
        @Override
        public int hashCode() {
            return rowOrdinal;
        }
    }
}
andrerobot
  • 71
  • 3
  • 2
    thanks for the [mcve] - always a pleasure to have something reproducible :) As both @jewelsea and me see the change between versions, you might consider reporting this as a regression - only if they knew about it, they can fix it :) – kleopatra Mar 30 '23 at 09:52

2 Answers2

6

Has there been a performance regression?

Yes.

I ran the example as provided on OpenJDK 20 + JavaFX 20 OS X x64 13.2.1, 2020 MacBook Pro. With large numbers of rows (e.g. 1,000,000+), performance was slow when scrolling with the mouse via the scroll bar. With 1,000 rows, the scrolling performance was good.

With OpenJDK 8b251 scrolling performance was quick for 10,000,000 rows.

It looks like this is a known issue, as pointed out by Maatr:

If you consider your issue to be a different problem, you may consider submitting a bug report.

Workarounds

Use a JavaFX version between 8 and 17 inclusive. From my local testing, scrolling of TableViews with large data sets performs acceptably.

For other JavaFX versions, you could search for workarounds that will improve the performance of the TableView when backed by large data sets, but I wouldn't advise doing that unless you are skilled and motivated to do so. Instead, if a version of JavaFX is required that exhibits this performance issue, rework your UI and logic so that you don't load a lot of items into the TableView backing list. Perhaps, restrict the maximum number of items to 10,000 or so and handle large volumes of data via pagination.

any idea when this started?

The major TableView scrolling performance issue with large datasets started in JavaFX version 18.

For the JDK 8b251 version I mentioned, scrolling performance for 1,000 rows or 10,000,000 is the same (excellent).

For the JavaFX 17.0.2 version (on OS X x64 13.2.1, OpenJDK 20), scrolling performance for 10,000,000 rows will slightly stutter but is generally fine. Performance is a bit worse than the good performance for 1,000 rows, but not really an issue.

For the JavaFX 18.0.2, 19.0.2.1, and 20 versions (on OS X x64 13.2.1, OpenJDK 20), scrolling performance for 10,000,000 rows will greatly stutter. Performance is much worse than the good performance for 1,000 rows.

These results are from subjective visual observation of local testing in my environment. Results and subjective observations by you from your testing in your environment may differ, but will likely be generally similar.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • 2
    any idea when this started? – kleopatra Mar 29 '23 at 22:46
  • I ran some checks against various JavaFX versions and updated the answer to report findings. – jewelsea Mar 30 '23 at 00:17
  • can see the same subjective visual observation (with a dev version of fx20 - not yet changed to release) on win - definitely there was a change that degraded (scrolling?) performance. From a UX perspective it's plain crazy to have those many rows, but if whatever scheme was implemented was usable before and not usable after .. it should be fixed asap. There have been changes to VirtualFlow during fx20, but didn't really followed them. – kleopatra Mar 30 '23 at 09:42
  • Wow- thank you for the exhaustive analysis. Going to follow advice here and test with recommended JDK + JFX combos. – andrerobot Mar 30 '23 at 11:30
1

There is a similar TableView performance issue filled: https://bugs.openjdk.org/browse/JDK-8293836

Edit 2023-06-16: Looks like it is fixed and can be tested with 21-ea+21 version

Maatr
  • 38
  • 4