Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8193445: JavaFX CSS is applied redundantly leading to significant performance degradation #34

Closed

Conversation

aghaisas
Copy link
Collaborator

@aghaisas aghaisas commented Nov 12, 2019

Issue :
https://bugs.openjdk.java.net/browse/JDK-8193445

Background :
The CSS performance improvement done in JDK-8151756 had to be backed out due to functional regressions reported in JDK-8185709, JDK-8183100 and JDK-8168951.
Refer to JDK-8183100 for more details on this backout.

Description :
This PR reintroduces the CSS performance improvement fix done in JDK-8151756 while addressing the functional regressions that were reported in JDK-8185709, JDK-8183100 and JDK-8168951.
For ease of review, I have made two separate commits -

  1. Commit 1 - Reintroduces the CSS performance improvement fix done in JDK-8151756 - most of the patch applied cleanly.
  2. Commit 2 - fixes the functional regressions keeping performance improvement intact + adds a system test

Root Cause :
CSS performance improvement fix proposed in JDK-8151756 correctly avoids the redundant CSS reapplication to children of a Parent.
What was missed earlier in JDK-8151756 fix : "CSS reapplication to the Parent itself”.
This missing piece was the root cause of all functional regressions reported against JDK-8151756

Fix :
Fixed the identified root cause. See commit 2.

Testing :

  1. All passing unit tests continue to pass
  2. New system test (based on JDK-8209830) added in this PR - fails before this fix and passes after the fix
  3. System test JDK8183100Test continues to pass
  4. All test cases attached to regressions JDK-8185709, JDK-8183100 and JDK-8168951 pass with this fix

In addition, testing by community with specific CSS performance / functionality will be helpful.

Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed

Issue

JDK-8193445: JavaFX CSS is applied redundantly leading to significant performance degradation

Approvers

  • Kevin Rushforth (kcr - Reviewer)
  • David Grieve (dgrieve - Reviewer) Note! Review applies to 4dade6e

@bridgekeeper
Copy link

bridgekeeper bot commented Nov 12, 2019

👋 Welcome back aghaisas! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request (refresh this page to view it).

@aghaisas aghaisas changed the title [WIP] JDK-8193445: JavaFX CSS is applied redundantly leading to significant performance degradation 8193445: JavaFX CSS is applied redundantly leading to significant performance degradation Nov 12, 2019
@openjdk openjdk bot added the rfr Ready for review label Nov 12, 2019
@mlbridge
Copy link

mlbridge bot commented Nov 12, 2019

Webrevs

@aghaisas
Copy link
Collaborator Author

Reviewers: @kevinrushforth and @arapte

@aghaisas
Copy link
Collaborator Author

It will be helpful if I can get some feedback from community with additional testing apart from what is mentioned in the description.

@dsgrieve
Copy link
Member

dsgrieve commented Nov 13, 2019

Has this been checked with SubScene? Two cases would be 1) SubScene inheriting a style from its parent, and 2) behavior of a parent in the SubScene itself. I don't expect that this would be an issue, but there is some special handling of CSS in SubScene IIRC.

@DeanWookey
Copy link
Contributor

Having looked at this issue previously (https://mail.openjdk.java.net/pipermail/openjfx-dev/2018-November/022842.html and DeanWookey/openjdk-jfx@65a1ed8), I'm a bit confused.

Doesn't commit 12ea822 essentially add another reapplyCSS() above the scenesChanged call on line 1074? I'm guessing this works because reapplyCss() (different from reapplyCSS()) sets cssFlag to UPDATE, which then means subsequent calls to reapplyCSS() don't call reapplyCss()?

Does this leave the whole tree with the CssFlags.REAPPLY set instead of CssFlags.UPDATE as they would be without these changes? I'm not sure about the impact of that.

I'm also curious whether commit 1 is even necessary with commit 2.

@kevinrushforth
Copy link
Member

The above raises good questions about the effect of adding a call to reapplyCSS when scenesChanged is called. This will need a careful analysis.

Regarding whether commit 2 by itself would be sufficient, my initial guess was that it wouldn't be. However, I tested it, and it does in fact improve performance of the test case. Very interesting.

@kevinrushforth
Copy link
Member

Looking at this a little further, the change from commit 2, to call reapplyCSS at the beginning of scenesChanged really does two things:

  1. Calling reapplyCSS right at the beginning of scenesChanged flips the order of CSS reapplication from the existing "bottom up" order to a "top down" order. Note that scenesChanged calls invalidatedScenes recursively (via setScenes) to traverse the scene graph.

  2. It effectively undoes the rest of the changes, which were intended to avoid calling reapplyCSS in cases where it is redundant. As long as the performance improvement is preserved, this seems fine.

The first of the above appears to be both necessary and sufficient to both maintain correctness while still avoiding the n^2 application of CSS in the case of adding a deep scene graph. If this is in fact the case, then I think the fix can be simplified even further by moving the following call from its current location (after updateTreeShowing) to right before the call to scenesChanged:

    if (sceneChanged) reapplyCSS();

in which case the changes to Node::scenesChanged and Parent::scenesChanged can be reverted

The following patch, by itself (with nothing from commit 1) might be sufficient:

--- a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java
+++ b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java
@@ -1069,6 +1069,7 @@ public abstract class Node implements EventTarget, Styleable {
             focusSetDirty(oldScene);
             focusSetDirty(newScene);
         }
+        if (sceneChanged) reapplyCSS(); // Note this can be moved inside the previous 'if' block
         scenesChanged(newScene, newSubScene, oldScene, oldSubScene);

         // isTreeShowing needs to take into account of Window's showing
@@ -1091,8 +1092,6 @@ public abstract class Node implements EventTarget, Styleable {
         }
         updateTreeShowing();

-        if (sceneChanged) reapplyCSS();
-
         if (sceneChanged && !isDirtyEmpty()) {
             //Note: no need to remove from scene's dirty list
             //Scene's is checking if the node's scene is correct

We will need to do a more complete evaluation as to whether this inversion of calling reapplyCSS on the parent before its children has any side effects, and also whether there are any of the performance improvements from the original fix that aren't covered here.

@DeanWookey
Copy link
Contributor

I think inverting the call is fine. That's what I did in my fix (DeanWookey/openjdk-jfx@65a1ed8) and we've been testing that out thoroughly for over a year.

It's as if you are adding nodes 1 by 1 to the scene graph, something we know works and is fast. My change tries to emulate that more accurately to avoid side effects. Theoretically, we should be able to do better when many nodes are added at once because we have all the information upfront.

The one side effect I can see by only applying commit 2 is that the first call of reapplyCSS() calls reapplyCss on every node in the tree and that sets the cssFlag = CssFlags.UPDATE;. The subsequent calls will hit this in reapplyCSS():

        if (cssFlag == CssFlags.UPDATE) {
            cssFlag = CssFlags.REAPPLY;
            notifyParentsOfInvalidatedCSS();
            return;
        }

and return without doing all the unnecessary work. As a result however, instead of leaving with cssFlag = CssFlags.UPDATE, all the nodes leave with CssFlags.REAPPLY. That might cause an unnecessary css pass later?

Doing it in the order it happens now, that check for the update flag shouldn't be true because its bottom up.

@dsgrieve
Copy link
Member

I think the order of calls in invalidatedScenes is correct. The scene graph plumbing needs to be done before CSS is applied since a node's style can depend on the styles of its parent.

The issue is really calling reapplyCss(). In the case where a child is added to a parent, the child's parent property is invalidated which should call reapplyCSS(), which should (in turn) call reapplyCss(). Next the invalidatedScenes call happens and the whole dance starts again. (See Parent children.onChanged).

Perhaps short-circuiting the call to reapplyCss() from the reapplyCSS() method is the thing to do. Since CSS is reapplied from the top down, my parent's cssFlag should be CLEAN before reapplyCss() method is called from reapplyCSS().

It has been a long time since I've been in this code. I'm going to look at this some more.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we are still discussing the fix itself, I added a few comments on the new test. It generally looks good, but should be run on a variety of systems, with and without the fix (once we have a final fix that we are satisfied with).

@aghaisas
Copy link
Collaborator Author

I think inverting the call is fine. That's what I did in my fix (DeanWookey/openjdk-jfx@65a1ed8) and we've been testing that out thoroughly for over a year.

It's as if you are adding nodes 1 by 1 to the scene graph, something we know works and is fast. My change tries to emulate that more accurately to avoid side effects. Theoretically, we should be able to do better when many nodes are added at once because we have all the information upfront.

The one side effect I can see by only applying commit 2 is that the first call of reapplyCSS() calls reapplyCss on every node in the tree and that sets the cssFlag = CssFlags.UPDATE;. The subsequent calls will hit this in reapplyCSS():

        if (cssFlag == CssFlags.UPDATE) {
            cssFlag = CssFlags.REAPPLY;
            notifyParentsOfInvalidatedCSS();
            return;
        }

and return without doing all the unnecessary work. As a result however, instead of leaving with cssFlag = CssFlags.UPDATE, all the nodes leave with CssFlags.REAPPLY. That might cause an unnecessary css pass later?

Doing it in the order it happens now, that check for the update flag shouldn't be true because its bottom up.

It is a good observation about cssFlag. I have not seen any side effect with the limited testing that I have done. It may be possible that the "unnecessary css pass later" scenario is not covered by the test cases that we have.

@aghaisas
Copy link
Collaborator Author

Perhaps short-circuiting the call to reapplyCss() from the reapplyCSS() method is the thing to do.

This comment from @dsgrieve got me interested. I checked the test code JDK-8151756 with cssFlags logged, it became evident that the cssFlag gets set to DIRTY_BRANCH for every parent and reapplyCss() gets invoked for each of the children. This is the exact redundant processing.

Test from JDK-8151756 with additional one level of Node hierarchy.

Parent1<--Parent2<--Parent3<--Rectangle (leaf child)

Log from test program ----
Parent 1 : VBox@1d9e402b
Parent 2 : VBox@4cc2dcce
Parent 3 : VBox@4cc2dcce
Rectangle

REAPPLY_CSS called for : VBox@1d9e402b ----- CssFlags.CLEAN
REAPPLY_CSS called for : Rectangle[...] ----- CssFlags.CLEAN

reapplyCss called for : Rectangle[...] ----- CssFlags.CLEAN
REAPPLY_CSS called for : VBox@19234c0d ----- CssFlags.DIRTY_BRANCH
reapplyCss called for : VBox@19234c0d ----- CssFlags.DIRTY_BRANCH
reapplyCss called for : Rectangle[...] ----- CssFlags.CLEAN
REAPPLY_CSS called for : VBox@4cc2dcce ----- CssFlags.DIRTY_BRANCH
reapplyCss called for : VBox@4cc2dcce ----- CssFlags.DIRTY_BRANCH
reapplyCss called for : VBox@19234c0d ----- CssFlags.UPDATE
reapplyCss called for : Rectangle[...] ----- CssFlags.CLEAN
REAPPLY_CSS called for : VBox@1d9e402b ----- CssFlags.DIRTY_BRANCH
reapplyCss called for : VBox@1d9e402b ----- CssFlags.DIRTY_BRANCH
reapplyCss called for : VBox@4cc2dcce ----- CssFlags.UPDATE
reapplyCss called for : VBox@19234c0d ----- CssFlags.UPDATE
reapplyCss called for : Rectangle[...] ----- CssFlags.CLEAN

Proposed New Fix :

I added a simple check to avoid reapplyCss() call for each Node with DIRTY_BRANCH cssFlag. Here is the patch -

diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java
index 877e0fd6c8..8606dfb575 100644
--- a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java
+++ b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java
@@ -9416,7 +9416,7 @@ public abstract class Node implements EventTarget, Styleable {
         if (cssFlag == CssFlags.REAPPLY) return;
 
         // RT-36838 - don't reapply CSS in the middle of an update
-        if (cssFlag == CssFlags.UPDATE) {
+        if (cssFlag == CssFlags.UPDATE || cssFlag == CssFlags.DIRTY_BRANCH) {
             cssFlag = CssFlags.REAPPLY;
             notifyParentsOfInvalidatedCSS();
             return;

With this fix -
Log from test program ----
Parent 1 : VBox@36d24c70
Parent 2 : VBox@35af5cea
Parent 3 : VBox@35af5cea
Rectangle

REAPPLY_CSS called for : VBox@36d24c70 ----- CssFlags.CLEAN
REAPPLY_CSS called for : Rectangle[...] ----- CssFlags.CLEAN
reapplyCss called for : Rectangle[...] ----- CssFlags.CLEAN
REAPPLY_CSS called for : VBox@5d4b6983 ----- CssFlags.DIRTY_BRANCH
REAPPLY_CSS called for : VBox@35af5cea ----- CssFlags.DIRTY_BRANCH
REAPPLY_CSS called for : VBox@36d24c70 ----- CssFlags.DIRTY_BRANCH

reapplyCss called for : VBox@36d24c70 ----- CssFlags.REAPPLY
reapplyCss called for : VBox@35af5cea ----- CssFlags.REAPPLY
reapplyCss called for : VBox@5d4b6983 ----- CssFlags.REAPPLY
reapplyCss called for : Rectangle[...] ----- CssFlags.CLEAN

I verified that all graphics/controls unit tests & all system tests pass with this fix.
I launched and played with Ensemble app. I did not see any visible artifacts.

@dsgrieve
Copy link
Member

@aghaisas You can avoid the call to notifyParentsOfInvalidatedCSS in the case where the flag is DIRTY_BRANCH.

I like the looks of this. From the 10,000 foot view, when a node's parent changes, or a node's scene changes, CSS should be reapplied. This is exactly what 'if (sceneChanged) reapplyCSS()' says, and what happens in parent property's invalidated method. All of the optimizations (do I really need to reapply css?) happen elsewhere, so I like this solution better than passing a boolean around (the original patch).

@aghaisas
Copy link
Collaborator Author

aghaisas commented Nov 21, 2019

Thanks @dsgrieve for having a look. I have updated the PR as suggested to avoid call to notifyParentsOfInvalidatedCSS in case the flag is DIRTY_BRANCH.
Also, I have modified the system test as suggested by @kevinrushforth.

Kindly review.

Limited testing shows that this fix holds up good.

@dsgrieve
Copy link
Member

Trying to run this, but have to build on Windows. Ugh!

@aghaisas
Copy link
Collaborator Author

Request to @DeanWookey, @tomsontom, @swpalmer - can you please confirm if this fix helps your application or tests?

@Groostav
Copy link

Groostav commented Nov 22, 2019

Does the performance problem addressed here relate to reducing heap space allocations? My profiler pointed me at this issue, and around the same time I'm seeing OutOfMemoryErrors. I'm wondering if (or rather, hoping) they're the same problem.

@aghaisas
Copy link
Collaborator Author

aghaisas commented Nov 25, 2019

I did a quick check w.r.t. memory using profiler. This patch significantly reduces the heap allocations as well.
There is a win on both - performance & memory fronts.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix itself looks good and is a much safer approach than the previous one. I've done a fair bit of testing and can see no regressions as a result of this fix. I did find one unrelated issue while testing (a visual bug introduced back in JDK 10) that I will file separately.

The test is pretty close, but still needs a few changes. The main problem is that it does not catch the performance problem, meaning if you run it without the fix, it will still pass. Your test class extends javafx.application.Application, which can cause problems, since JUnit will create a new instance of the test class that is different from the one created by the call to Application.launch in the @BeforeClass method. This in turn leads to the rootPane instance used by the test method being different from the one used as the root of the visible scene. The fix is to use a separate nested (static) subclass of Application, and make the rootPane field a static field. I have left inline comments for this and a few other things I noticed.

@openjdk openjdk bot removed the rfr Ready for review label Nov 26, 2019
@openjdk
Copy link

openjdk bot commented Nov 26, 2019

@aghaisas This change can now be integrated. The commit message will be:

8193445: JavaFX CSS is applied redundantly leading to significant performance degradation

Reviewed-by: kcr, dgrieve
  • If you would like to add a summary, use the /summary command.
  • To list additional contributors, use the /contributor command.

Since the source branch of this PR was last updated there have been 17 commits pushed to the master branch:

  • 798afbc: 8230610: Upgrade GStreamer to version 1.16.1
  • 126896d: 8234704: Fix attribution in libxslt.md
  • 4d3c723: 8234593: Mark LeakTest.testGarbageCollectability as unstable
  • 5a39824: 8234056: Upgrade to libxslt 1.1.34
  • 8bea7b7: 8229472: Deprecate for removal JavaBeanXxxPropertyBuilders constructors
  • aad1720: 8233420: Upgrade to gcc 8.3 on Linux
  • 42040c4: 8232063: Upgrade gradle to version 6.0
  • aab07a4: 8234239: [TEST_BUG] Reenable few ignored web tests
  • 95ad601: 8233421: Upgrade to Visual Studio 2017 version 15.9.16
  • 3e0557a: 8234303: [TEST_BUG] Correct ignore tag in graphics unit tests
  • e37cb37: 8234150: Address ignored tests in ComboBoxTest, LabeledTest, HyperLinkTest and TextInputControlTest
  • 4f496d4: 8234194: [TEST_BUG] Reenable few graphics unit tests
  • 927fc8a: 8234174: Change IDEA VCS mapping to Git
  • 3d0cb49: 8234189: [TEST_BUG] Remove ignored and invalid graphics unit tests
  • dc01309: 8234110: SwingFXUtilsTest is unsuitable for unit test framework
  • 5b96ee4: 8231188: Update SQLite to version 3.30.1
  • d46dcae: 8233338: FX javadoc headings are out of sequence

Since there are no conflicts, your changes will automatically be rebased on top of the above commits when integrating. If you prefer to do this manually, please merge master into your branch first.

  • To integrate this PR with the above commit message, type /integrate in a new comment.

@openjdk openjdk bot added the ready Ready to be integrated label Nov 26, 2019
@DJViking
Copy link

DJViking commented Nov 26, 2019

I am curious. Will∕Could this CSS performance improvement be backported to JavaFX 11? The bug report says only that it will be fixed in JavaFX 14.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

@aghaisas
Copy link
Collaborator Author

/integrate

@openjdk openjdk bot closed this Nov 27, 2019
@openjdk openjdk bot added integrated Pull request has been integrated and removed ready Ready to be integrated labels Nov 27, 2019
@openjdk
Copy link

openjdk bot commented Nov 27, 2019

@aghaisas The following commits have been pushed to master since your change was applied:

  • 798afbc: 8230610: Upgrade GStreamer to version 1.16.1
  • 126896d: 8234704: Fix attribution in libxslt.md
  • 4d3c723: 8234593: Mark LeakTest.testGarbageCollectability as unstable
  • 5a39824: 8234056: Upgrade to libxslt 1.1.34
  • 8bea7b7: 8229472: Deprecate for removal JavaBeanXxxPropertyBuilders constructors
  • aad1720: 8233420: Upgrade to gcc 8.3 on Linux
  • 42040c4: 8232063: Upgrade gradle to version 6.0
  • aab07a4: 8234239: [TEST_BUG] Reenable few ignored web tests
  • 95ad601: 8233421: Upgrade to Visual Studio 2017 version 15.9.16
  • 3e0557a: 8234303: [TEST_BUG] Correct ignore tag in graphics unit tests
  • e37cb37: 8234150: Address ignored tests in ComboBoxTest, LabeledTest, HyperLinkTest and TextInputControlTest
  • 4f496d4: 8234194: [TEST_BUG] Reenable few graphics unit tests
  • 927fc8a: 8234174: Change IDEA VCS mapping to Git
  • 3d0cb49: 8234189: [TEST_BUG] Remove ignored and invalid graphics unit tests
  • dc01309: 8234110: SwingFXUtilsTest is unsuitable for unit test framework
  • 5b96ee4: 8231188: Update SQLite to version 3.30.1
  • d46dcae: 8233338: FX javadoc headings are out of sequence

Your commit was automatically rebased without conflicts.

Pushed as commit 83eb0a7.

@mlbridge
Copy link

mlbridge bot commented Nov 27, 2019

Mailing list message from Ajit Ghaisas on openjfx-dev:

Changeset: 83eb0a7
Author: Ajit Ghaisas
Date: 2019-11-27 07:05:15 +0000
URL: https://git.openjdk.java.net/jfx/commit/83eb0a7c

8193445: JavaFX CSS is applied redundantly leading to significant performance degradation

Reviewed-by: kcr, dgrieve

! modules/javafx.graphics/src/main/java/javafx/scene/Node.java

  • tests/system/src/test/java/test/javafx/scene/QuadraticCssTimeTest.java

@buedi
Copy link

buedi commented Dec 3, 2019

Looks good.
@kevinrushforth
Do you think this fix will be backported to JDK8\JavaFX8?

@kevinrushforth
Copy link
Member

Do you think this fix will be backported to JDK8\JavaFX8?

This has already been backported to FX 8 for Oracle's JDK 8u251.

@DJViking
Copy link

DJViking commented Dec 5, 2019

Do you think this fix will be backported to JDK8\JavaFX8?

This has already been backported to FX 8 for Oracle's JDK 8u251.

Will JavaFX 11 get the same treatment?

@kevinrushforth
Copy link
Member

Yes, this would be an excellent candidate to backport to JavaFX 11 (and one I was going to recommend).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

7 participants