On Sat, 2 Oct 2021 13:35:51 GMT, Kevin Rushforth <k...@openjdk.org> wrote:

>> The behavior is the same between openjdk 16.0.2 and 17.0.0.1. The test to 
>> recreate the problem adds many nodes (Rectangle) to a parent Group. The 
>> process causes each node to add two invalidation listeners (disabledProperty 
>> and treeVisibleProperty) to the parent node 
>> [here](https://github.com/openjdk/jfx/blob/64aa92631fa5aad9293553e8dd174eab647de2f3/modules/javafx.graphics/src/main/java/javafx/scene/Node.java#L965)
>>  using ExpressionHelper.Generic.addListener(). When the children are cleared 
>> from the group the inefficient linear search through the listener array and 
>> remove occurs, two linear searches per child.
>> 
>> Here is the minimal application I am using to recreate the issue.
>> 
>> 
>> public class ClearGroupPerformance extends Application {
>> 
>>     private static final int GAP = 10;
>>     private static final int SIZE = 40;
>>     private Group groupBase;
>> 
>>     public static void main(String[] args) {
>> 
>>         launch(args);
>>     }
>> 
>>     @Override
>>     public void start(Stage stage) {
>> 
>>         int width = 10000;
>>         int height = 10000;
>> 
>>         ScrollPane scrollPane = new ScrollPane();
>>         StackPane root = createGroupRoot(width, height);
>>         scrollPane.setContent(root);
>> 
>>         Button clearGroupButton = new Button("Clear Group");
>>         clearGroupButton.onActionProperty().set(e -> {
>> 
>>             long startTime = System.nanoTime();
>>             ObservableList<Node> children = groupBase.getChildren();
>>             int totalChildren = children.size();
>>             children.clear();
>>             System.out.printf("Clearing %d nodes took %.2fms%n", 
>> totalChildren, (System.nanoTime()-startTime)/1_000_000.0);
>> 
>>         });
>> 
>>         BorderPane borderPane = new BorderPane();
>>         borderPane.setTop(clearGroupButton);
>>         borderPane.setCenter(scrollPane);
>> 
>>         Scene scene = new Scene(borderPane, 100, 100);
>>         stage.setScene(scene);
>>         stage.show();
>> 
>>     }
>> 
>>     private StackPane createGroupRoot(int width, int height) {
>> 
>>         groupBase = new Group();
>>         groupBase.getChildren().add(new Rectangle(width, height, new 
>> Color(0.5, 0.5, 0.5, 1.0)));
>> 
>>         for (int posX = GAP; posX + SIZE + GAP < width; posX += SIZE + GAP) {
>>             for (int posY = GAP; posY + SIZE + GAP < height; posY += SIZE + 
>> GAP) {
>>                 Rectangle rectangle = new Rectangle(posX, posY, SIZE, SIZE);
>>                 rectangle.setFill(Color.GREEN);
>> 
>>                 groupBase.getChildren().add(rectangle);
>>             }
>>         }
>> 
>>         return new StackPane(groupBase);
>>     }
>> 
>> }
>
>> Here is the minimal application I am using to recreate the issue.
> 
> I'll attach the test case you provided to the bug report.
> 
>> The question now is should the work done on this PR be abandoned. It 
>> addresses a current performance limitation taking complexity from O(n log n) 
>> to O(1).
> 
> That is a fair question, but it begs two additional questions. Are there 
> enough real world examples where performance is being hurt by using an 
> ArrayList to justify the effort and risk (more on that below)? If so, are 
> there other solutions that would reduce the number of listeners needed? The 
> original problems that motivated this fix were largely addressed by 
> [JDK-8252935](https://bugs.openjdk.java.net/browse/JDK-8252935) / PR #185 and 
> [JDK-8252811](https://bugs.openjdk.java.net/browse/JDK-8252811) / PR #298.
> 
>> It appears to be well isolated with low risk to backwards compatibility. The 
>> reason work was stopped was a concern that the tests should go first in a 
>> separate PR.
> 
> I disagree that this is a low risk change. This proposed fix touches a 
> fundamental area used by all bindings, and does so in a way that will 
> increase memory usage -- and might negatively impact performance -- in the 
> common case of a small number of listeners. By unconditionally switching from 
> an `ArrayList` to a `HashSet`, the memory required is significantly 
> increased, which will be most noticeable for the common case of only a few 
> listeners. An adaptive solution, which starts out using an `ArrayList` and 
> dynamically switches to a `LinkedHashSet` (which preserves notification 
> order...I know it isn't specified, but changing that is likely to break some 
> applications) when crossing some threshold of the number of listeners, could 
> mitigate this concern at the cost of added complexity.
> 
> If you still want to pursue this, you will need to do a fair amount of work 
> to provide the additional tests that will be needed, to motivate the need for 
> this enhancement (given that the original reasons are mostly addressed), and 
> to resolve the issues that were raised in this PR. The mechanics of doing 
> this are pretty easy. First, read the [CONTRIBUTING 
> guideline](https://github.com/openjdk/jfx/blob/master/CONTRIBUTING.md), 
> specifically the part about signing the OCA, and create a new PR starting 
> from this one. You would then add the author of this PR as a contributor.
> 
> In the mean time, I'm going to move this PR to Draft (which I should have 
> done long ago), since it stalled and not being actively worked on or reviewed.

@kevinrushforth makes an important point I was missing. Functionally this 
change is isolated but the memory overhead of a new data structure used by 
every node would risk performance degradation across many existing 
applications. I now see that the recommendation @hjohn to avoid adding 
unnecessary listeners by default should be prioritized over a faster listener 
lookup data structure. This will clarifies why work on this PR is currently 
back in draft status.

-------------

PR: https://git.openjdk.java.net/jfx/pull/108

Reply via email to