[PATCH] Decrease Toolkit.firePulse allocation rate
Doswald Michael
Michael.Doswald at schiller.ch
Thu Jan 14 12:52:51 UTC 2016
While profiling a JavaFX application that runs on embedded hardware, I have found that the Toolkit.firePulse method creates more garbage than necessary. In an application that simply animates an object without doing much else, I see that the firePulse method allocates a fair amount of objects (compared to the overall number of allocated objects). I have written a small patch that decreases the allocation rate in said method. Since I'm not sure if such tweaks are desired in the JavaFX codebase, I didn't open a JIRA issue. I'm happy to do so if they are.
The following small application animates a single circle on screen. The allocation rate of the 'JavaFX Application Thread' is as follows (measured with jvisualvm):
openjfx 8u HEAD: ~ 65 KB/s after start, ~ 49 KB/s after JIT compilation kicked in
with patch: ~ 25 KB/s, doesn't seem to change when JIT kicks in
public class AllocateApp extends Application {
public static void main(String... args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
Circle circle = new Circle(10, 25, 10);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(2), new KeyValue(circle.centerXProperty(), 490)));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.play();
primaryStage.setScene(new Scene(new Pane(circle), 500, 50));
primaryStage.show();
}
}
The patch below only creates a single List object instead of three WeakHashMaps to make a local copy of the TKPulseListeners. It also uses the number of listeners in the maps to estimate the size of the list. The patch uses WeakReference objects in the list, which I doubt is necessary, but it emulates the previous behaviour more accurately. I believe it would be possible to change that to a strong reference for the following reasons:
a) The list is local and the strong references would only be there for the time the firePulse method is run
b) The code would become more readable (less generics parameters, null-guard not necessary in loop)
c) Getting rid of the WeakReference objects would decrease garbage generation even more (down to ~ 21 KB/s)
Regards
Michael
diff -r f1c3eb85af4d modules/graphics/src/main/java/com/sun/javafx/tk/Toolkit.java
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/Toolkit.java Fri Jan 08 08:11:51 2016 -0800
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/Toolkit.java Wed Jan 13 10:43:27 2016 +0100
@@ -51,7 +51,6 @@
import javafx.scene.shape.StrokeType;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Modality;
-import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import java.io.File;
@@ -94,6 +93,8 @@
import com.sun.scenario.effect.Color4f;
import com.sun.scenario.effect.FilterContext;
import com.sun.scenario.effect.Filterable;
+import java.lang.ref.WeakReference;
+import javafx.util.Pair;
public abstract class Toolkit {
@@ -362,32 +363,36 @@
// and those changes propogated to scene before it gets its pulse to update
// Copy of listener map
- final Map<TKPulseListener,AccessControlContext> stagePulseList =
- new WeakHashMap<TKPulseListener,AccessControlContext>();
- final Map<TKPulseListener,AccessControlContext> scenePulseList =
- new WeakHashMap<TKPulseListener,AccessControlContext>();
- final Map<TKPulseListener,AccessControlContext> postScenePulseList =
- new WeakHashMap<TKPulseListener,AccessControlContext>();
+ final List<Pair<WeakReference<TKPulseListener>,AccessControlContext>> listenersList;
synchronized (this) {
- stagePulseList.putAll(stagePulseListeners);
- scenePulseList.putAll(scenePulseListeners);
- postScenePulseList.putAll(postScenePulseListeners);
+ listenersList = new ArrayList<>(stagePulseListeners.size()+scenePulseListeners.size()+postScenePulseListeners.size());
+ copyListeners(stagePulseListeners, listenersList);
+ copyListeners(scenePulseListeners, listenersList);
+ copyListeners(postScenePulseListeners, listenersList);
}
- for (Map.Entry<TKPulseListener,AccessControlContext> entry : stagePulseList.entrySet()) {
- runPulse(entry.getKey(), entry.getValue());
- }
- for (Map.Entry<TKPulseListener,AccessControlContext> entry : scenePulseList.entrySet()) {
- runPulse(entry.getKey(), entry.getValue());
- }
- for (Map.Entry<TKPulseListener,AccessControlContext> entry : postScenePulseList.entrySet()) {
- runPulse(entry.getKey(), entry.getValue());
+
+ for (int idx = 0, max = listenersList.size(); idx < max; idx++) {
+ Pair<WeakReference<TKPulseListener>, AccessControlContext> listenerEntry = listenersList.get(idx);
+ TKPulseListener pulseListener = listenerEntry.getKey().get();
+ if (pulseListener != null) {
+ runPulse(pulseListener, listenerEntry.getValue());
+ }
}
if (lastTkPulseListener != null) {
runPulse(lastTkPulseListener, lastTkPulseAcc);
}
}
+
+ private void copyListeners(Map<TKPulseListener, AccessControlContext> listenerMap, List<Pair<WeakReference<TKPulseListener>,AccessControlContext>> listenersList) {
+ if (!listenerMap.isEmpty()) {
+ for (Map.Entry<TKPulseListener,AccessControlContext> entry : listenerMap.entrySet()) {
+ listenersList.add(new Pair<>(new WeakReference<>(entry.getKey()), entry.getValue()));
+ }
+ }
+ }
+
public void addStageTkPulseListener(TKPulseListener listener) {
if (listener == null) {
return;
More information about the openjfx-dev
mailing list