1

I am encountering performance issues when generating large amount of nodes (say 1000) from FXML. If I create the exact node using API, it is much faster (appx 20 times). Is there any way I can increase the performance when generating the nodes from FXML.

Consider the below example. In this example when I press Generate button, I am populating 1000 layout instances from the fxml file. It is approx taking 10sec to generate them. Instead of Fxml, if I create the nodes using API, it is creating them in appx 600ms. For demo purpose I am saying 1000 nodes, in reality it will be much bigger number. enter image description here

Output when using FXML

Took 9191ms to generate 1000 nodes
Took 11895ms to generate & layout 1000 nodes

Output when using API:

Took 618ms to generate 1000 nodes
Took 3242ms to generate & layout 1000 nodes

Please note that I cannot use any control/layout that uses VirtualFlow. I need to generate all the nodes for other functionality which draws the joining lines between the nodes (like a UML diagram). I have multiple layouts designed using fxml. Unfortunately I cannot switch to API method.

Below is the complete working code:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class FxmlNodesDemo extends Application {
    public void start(Stage stage) throws Exception {

        Pane scrollContent = new Pane();
        Button generate = new Button("Generate");
        generate.setOnAction(e -> {
            System.out.println("Generating...");
            long start = System.currentTimeMillis();
            scrollContent.needsLayoutProperty().addListener((obs, old, val) -> {
                if (!val) {
                    System.out.println("Took " + (System.currentTimeMillis() - start) + "ms to generate & layout 1000 nodes");
                }
            });
            generate.setDisable(true);
            double y = 0;
            for (int i = 0; i < 1000; i++) {
                try {
                    // FXML approach
                    GridPane pane = FXMLLoader.load(getClass().getResource("nodeLayout.fxml"));
                    
                    // API approach
                    // GridPane pane = buildPane();
                    pane.setLayoutX(i % 2 == 0 ? 0 : 100);
                    pane.setLayoutY(y);
                    y = y + 130;
                    scrollContent.getChildren().add(pane);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            System.out.println("Took " + (System.currentTimeMillis() - start) + "ms to generate 1000 nodes");
        });
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.setContent(scrollContent);
        scrollPane.setPadding(new Insets(10));
        VBox.setVgrow(scrollPane, Priority.ALWAYS);

        VBox layout = new VBox(generate, scrollPane);
        layout.setSpacing(10);
        layout.setPadding(new Insets(10));

        Scene scene = new Scene(layout, 650, 700);
        stage.setScene(scene);
        stage.setTitle("Fxml Nodes");
        stage.show();
    }

    /**
     * Exact replica of the fxml node.
     */
    private GridPane buildPane() {
        GridPane gridPane = new GridPane();
        gridPane.setAlignment(Pos.CENTER);
        gridPane.setStyle("-fx-border-width:1px;-fx-border-color:black;-fx-border-radius:5px;");
        gridPane.setPadding(new Insets(5));

        Label label = new Label("Number Format Error !");
        label.setAlignment(Pos.CENTER);
        label.setMaxWidth(Double.MAX_VALUE);
        label.setMaxHeight(Double.MAX_VALUE);
        label.setStyle("-fx-background-color:red;");
        gridPane.add(label, 0, 0, 3, 1);

        TextField a = new TextField();
        a.setId("a");
        gridPane.add(a, 0, 1);
        TextField b = new TextField();
        b.setId("b");
        gridPane.add(b, 1, 1);
        TextField c = new TextField();
        c.setId("c");
        gridPane.add(c, 2, 1);

        Button solveButton = new Button("Solve");
        solveButton.setMaxWidth(Double.MAX_VALUE);
        solveButton.setMaxHeight(Double.MAX_VALUE);
        gridPane.add(solveButton, 0, 2, 3, 1);

        Button clearButton = new Button("Clear");
        clearButton.setMaxWidth(Double.MAX_VALUE);
        clearButton.setMaxHeight(Double.MAX_VALUE);
        gridPane.add(clearButton, 0, 3, 3, 1);

        for (int i = 0; i < 3; i++) {
            ColumnConstraints cc = new ColumnConstraints();
            cc.setFillWidth(true);
            cc.setHgrow(Priority.ALWAYS);
            gridPane.getColumnConstraints().add(cc);
        }

        for (int i = 0; i < 4; i++) {
            RowConstraints rc = new RowConstraints();
            if (i == 1) {
                rc.setVgrow(Priority.NEVER);
            } else {
                rc.setFillHeight(true);
                rc.setVgrow(Priority.ALWAYS);
            }
            gridPane.getRowConstraints().add(rc);
        }
        return gridPane;
    }

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

nodeLayout.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<GridPane xmlns:fx="http://javafx.com/fxml" alignment="center" style="-fx-border-width:1px;-fx-border-color:black;-fx-border-radius:5px;">
    <Label fx:id="label" text="Number Format Error !" maxWidth="Infinity" maxHeight="Infinity" style="-fx-background-color:red;" alignment="CENTER" GridPane.columnSpan="3"/>
    <TextField fx:id="a" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
    <TextField fx:id="b" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
    <TextField fx:id="c" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
    <Button fx:id="solveButton" text="Solve" maxWidth="Infinity" maxHeight="Infinity" GridPane.columnSpan="3" GridPane.rowIndex="2"  />
    <Button fx:id="clearButton" text="Clear" maxWidth="Infinity" maxHeight="Infinity" GridPane.columnSpan="3" GridPane.rowIndex="3" />
    <columnConstraints>
        <ColumnConstraints fillWidth="true" hgrow="ALWAYS"/>
        <ColumnConstraints fillWidth="true" hgrow="ALWAYS"/>
        <ColumnConstraints fillWidth="true" hgrow="ALWAYS"/>
    </columnConstraints>
    <rowConstraints>
        <RowConstraints fillHeight="true" vgrow="ALWAYS"/>
        <RowConstraints vgrow="NEVER"/>
        <RowConstraints fillHeight="true" vgrow="ALWAYS"/>
        <RowConstraints fillHeight="true" vgrow="ALWAYS"/>
    </rowConstraints>
    <padding>
        <Insets topRightBottomLeft="5"/>
    </padding>
</GridPane>
Sai Dandem
  • 8,229
  • 11
  • 26
  • 2
    Load the fxml in parallel on other threads (e.g. [fork/join](https://www.baeldung.com/java-fork-join) though not necessarily that) using a task or service. – jewelsea Dec 08 '21 at 05:12
  • @jewelsea thanks for the suggestion. will give a try using parallel loading. – Sai Dandem Dec 08 '21 at 05:17
  • 1
    I did profile your code and I found that `javafx.fxml.FXMLLoader.loadTypeForPackage()` is running around 45% of the time because this method calls `java.lang.ClassLoader.loadClass()` on every element of your fxml file 1000 times. I think writing your own class loader is one way to improve the poor performance of fxml file loading. – Miss Chanandler Bong Dec 08 '21 at 08:06
  • 1
    I am still wondering why you insist on using FXML in the first place. The fact that FXML is very slow is well known and does not come as a surprise. But without knowing your actual use case, it is difficult to give any reasonable advice. – mipa Dec 08 '21 at 08:41
  • @mipa, as I mentioned there are many templates(hundreds) designed using FXML. These templates are designed using SceneBuilder by programmers with non java skills. So it is very hard to migrate to API. – Sai Dandem Dec 08 '21 at 10:27
  • @MissChanandlerBong Thanks for providing the link. Will go through the post in detail. Looks it is line with my question.. – Sai Dandem Dec 08 '21 at 10:45
  • why do you read the fxml file every time? this will certainly reduce productivity. – mr mcwolf Dec 08 '21 at 11:58
  • Loading FXML is going to be considerably slower than constructing objects in the usual way, for a variety of reasons. You might be able to improve things by setting a custom class loader, or writing your own fxml parser, but it's always going to be considerably quicker to use an API-based approach. Your only real options here are to switch from FXML to Java, or perhaps to write an FXML compiler that does that job for you. – James_D Dec 08 '21 at 15:25
  • Did parallelization help at all? – jewelsea Dec 10 '21 at 05:14
  • 1
    @jewelsea, I am still trying to fit the parallelization in my application. Though using custom Classloader helps to get it a bit faster, still I feel it is bit lagging. Thinking to combine parallelization and custom ClassLoader to make it even faster. – Sai Dandem Dec 10 '21 at 06:04

0 Answers0