Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8222315

Font lookups are not always cached in CssStyleHelper leading to poor performance

XMLWordPrintable

    • x86_64
    • windows_10

      ADDITIONAL SYSTEM INFORMATION :
      Windows 10

      A DESCRIPTION OF THE PROBLEM :
      In CssStyleHelper, the getCachedFont method doesn't cache any font lookups itself. The result is only cached for the calling node. This means if a large node is added deep in the scene graph, getCachedFont traverses up the tree many times instead of caching the result the first time. There is already a mechanism for caching the font produced by this method, however only the original call's result is cached and not recursive calls.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Create a scene graph with a deep tree of nodes and relative (em) font sizes. Add a node with many text fields at the bottom.

      Alternatively run the code below.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The results after adding caching are as follows:

      Depth 50

      With setting font size: 11231ms
      Without setting font size: 21593ms

      Depth 100

      With setting font size: 16400ms
      Without setting font size: 52965ms

      ACTUAL -
      The results right now are as follows:

      Depth 50

      With setting font size: 10763ms
      Without setting font size: 49620ms

      Depth 100

      With setting font size: 15253ms
      Without setting font size: 144218ms

      Relative font sizes perform significantly worse.

      ---------- BEGIN SOURCE ----------

      import java.util.ArrayList;
      import java.util.concurrent.ExecutionException;
      import javafx.application.Application;
      import javafx.application.Platform;
      import javafx.beans.property.SimpleStringProperty;
      import javafx.beans.property.StringProperty;
      import javafx.beans.value.ObservableValue;
      import javafx.concurrent.Task;
      import javafx.scene.Scene;
      import javafx.scene.control.TableColumn;
      import javafx.scene.control.TableView;
      import javafx.scene.layout.StackPane;
      import javafx.stage.Stage;
      import javafx.util.Callback;

      public class FontSizeCssTest extends Application {

          int columns = 5;
          int numRows = 15;
          StackPane root = new StackPane();
          StackPane tableParent;

          @Override
          public void start(Stage primaryStage) {

              
              Scene scene = new Scene(root, 800, 600);
              primaryStage.setTitle("Hello World!");
              primaryStage.setScene(scene);
              primaryStage.show();
              root.setStyle("-fx-font-size:8pt;");
              StackPane curr = new StackPane();
              curr.setStyle("-fx-font-size:1em;"); //removing improves performance
              root.getChildren().add(curr);

              //performance is related to the depth of the scene graph
              for (int i = 0; i < 50; i++) {
                  StackPane child = new StackPane();
                  curr.getChildren().add(child);
                  child.setStyle("-fx-font-size:1em;"); //this makes the results significantly worse
                  curr = child;
              }

              tableParent = curr;

              Thread testThread = new Thread(() -> {
                  //warmup
                  for (int i = 0; i < 2; i++) {
                      withFontSize();
                      noFontSize();
                  }

                  //actual test
                  long start = System.currentTimeMillis();
                  withFontSize();
                  System.out.println("With setting font size: " + (System.currentTimeMillis() - start));
                  start = System.currentTimeMillis();
                  noFontSize();
                  System.out.println("Without setting font size: " + (System.currentTimeMillis() - start));
              });
              testThread.start();
          }

          public void noFontSize() {
              for (int i = 0; i < 500; i++) {
                  TableView table = getNewTestTableview();
                  Platform.runLater(() -> {
                      tableParent.getChildren().add(table);
                  });
                  waitForUIRender();
                  Platform.runLater(() -> {
                      tableParent.getChildren().clear();
                  });
              }
          }

          public void withFontSize() {
              for (int i = 0; i < 500; i++) {
                  TableView table = getNewTestTableview();
                  table.setStyle("-fx-font-size:8pt;");
                  Platform.runLater(() -> {
                      tableParent.getChildren().add(table);
                  });
                  waitForUIRender();
                  Platform.runLater(() -> {
                      tableParent.getChildren().clear();
                  });
              }
          }

          public TableView<StringProperty[]> getNewTestTableview() {
              TableView<StringProperty[]> tableView = new TableView<>();
              for (int i = 0; i < columns; i++) {
                  final int colIndex = i;
                  TableColumn<StringProperty[], String> col = new TableColumn<>();
                  col.setCellValueFactory(new Callback<>() {
                      @Override
                      public ObservableValue<String> call(TableColumn.CellDataFeatures<StringProperty[], String> param) {
                          return param.getValue()[colIndex];
                      }
                  });
                  tableView.getColumns().add(col);
              }

              ArrayList<StringProperty[]> rows = new ArrayList<>();

              for (int i = 0; i < numRows; i++) {
                  StringProperty[] row = new StringProperty[columns];
                  for (int j = 0; j < columns; j++) {
                      row[j] = new SimpleStringProperty("Row " + i + " Col " + j);
                  }
                  rows.add(row);
              }
              tableView.getItems().addAll(rows);
              return tableView;
          }

          /**
           * @param args the command line arguments
           */
          public static void main(String[] args) {
              launch(args);
          }

          public void waitForUIRender() {
              waitForAppThread();
              try {
                  Thread.sleep((long) (1000.0 / 60));
              } catch (InterruptedException ex) {
              }
              waitForAppThread();

          }

          private void waitForAppThread() {
              Task t = new Task() {
                  @Override
                  protected Object call() throws Exception {
                      return null;
                  }
              };
              Platform.runLater(t);
              try {
                  t.get();
              } catch (InterruptedException | ExecutionException ex) {
              }
          }

      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Setting an absolute font size on a node stops font size calculations traversing up beyond that node, which significantly improves performance.

      FREQUENCY : always


            arapte Ambarish Rapte
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: