On Fri, 17 Apr 2020 07:22:20 GMT, dannygonzalez <github.com+6702882+dannygonza...@openjdk.org> wrote:
>> I've tested this pull request locally a few times, and the performance >> improvement is quite significant. A test with 20.000 nested stack panes >> resulted in these average times: >> >> - Add all 51 ms >> - Remove all 25 ms >> >> Versus the unmodified code: >> >> - Add all 34 ms >> - Remove all 135 ms >> >> However, there are some significant functional changes as well that might >> impact current users: >> >> 1. The new code ensures that all listeners are notified even if the list is >> modified during iteration by always making a **copy** when an event is >> fired. The old code only did so when it was **actually** modified during >> iteration. This can be mitigated by making the copy in the code that >> modifies the list (as the original did) using the `locked` flag to check >> whether an iteration was in progress. >> >> 2. There is a significant increase in memory use. Where before each >> listener occupied an entry in an array, now each listener is wrapped by >> `Map.Entry` (the Integer instance used per entry can be disregarded). I >> estimate around 4-8x more heap will be consumed (the numbers are small >> however, still well below 1 MB for 20.000 entries). If this is an issue, a >> further level could be introduced in the listener implementation hierarchy >> (singleton -> array -> map). >> >> 3. Even though the current version of this pull request takes care to notify >> duplicate listeners the correct amount of times, it does not notify them in >> the correct order with respect to other listeners. If one registers >> listeners (a, b, a) the notification order will be (a, a, b). >> >> The last point is not easily solved and could potentially cause problems. >> >> Finally I think this solution, although it performs well is not the full >> solution. A doubling or quadrupling of nodes would again run into serious >> limitations. I think this commit >> https://github.com/openjdk/jfx/commit/e21606d3a1b73cd4b44383babc750a4b4721edfd >> should not have introduced another listener for each Node on the Window >> class. A better solution would be to only have the Scene listen to Window >> and have Scene provide a new combined status property that Node could use >> for its purposes. >> >> Even better however would be to change the properties involved to make use >> of the hierarchy naturally present in Nodes, having child nodes listen to >> their parent, and the top level node listen to the scene. This would reduce >> the amount of listeners on a single property in Scene and Window immensely, >> instead spreading those listeners over the Node hierarchy, keeping listener >> lists much shorter, which should scale a lot better. > > @hjohn > >> 2. There is a significant increase in memory use. Where before each listener >> occupied an entry in an array, now each listener is wrapped by Map.Entry >> (the Integer instance used per entry can be disregarded). I estimate around >> 4-8x more heap will be consumed (the numbers are small however, still well >> below 1 MB for 20.000 entries). If this is an issue, a further level could >> be introduced in the listener implementation hierarchy (singleton -> array >> -> map). > > There was discussion about lazy initialisation of the LinkedHashMap when > needed and/or have some sort of strategy where we could use arrays or lists > if the number of listeners are less than some threshold (i.e. introducing > another level to the hierarchy as you mentioned). > This was mentioned here also: > https://github.com/openjdk/jfx/pull/108#issuecomment-590838942 I've implemented an alternative solution: Removing the listeners on `Window.showingProperty` and `Scene.windowProperty` completely. They are in fact only used in two places: `PopupWindow` in order to remove itself if the `Window` is no longer showing, and `ProgressIndicatorSkin`. These two can be easily replaced with their own listeners for these properties instead of burdening **all** nodes with these listeners only to support these two classes. I left the `isTreeShowing` method in, and implemented it simply as `isTreeVisible() && isWindowShowing()` as that's really the only difference between "visible" and "showing" apparently. Here is the test result with 20.000 nested StackPanes with only this change in: - Add all 45 ms - Remove all 25 ms I think this might be a good solution as it completely avoids these listeners. @dannygonzalez I added a proof of concept here if you want to play with it: https://github.com/openjdk/jfx/pull/185 ------------- PR: https://git.openjdk.java.net/jfx/pull/108