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