On 1/14/16 8:09 PM, Jim Graham wrote:
> How often are listeners added and removed?  It might make more sense to
> make them copy-on-write instead...?

That is a good point. As far as I can see, every window adds a stage listener 
when it is shown and removes it if it is closed. Every scene adds/removes a 
scene listener. So the listener maps are changed rarely. The post scene pulse 
listener seems to be used for the snapshot functionality. Although I can only 
see that such a listener is added, but it seems it will never be removed again.

I will try to revise my patch and open an RFE as suggested by Kevin as soon as 
I handed in my OCA.

Regards,
Michael

>On 1/14/16 4:52 AM, Doswald Michael wrote:
>> 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;
>>
>>
>>
>>
>>
>>
>>
>>

Reply via email to