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

8322964: Optimize performance of CSS selector matching #1316

Open
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

hjohn
Copy link
Collaborator

@hjohn hjohn commented Jan 4, 2024

Improves performance of selector matching in the CSS subsystem. This is done by using custom set implementation which are highly optimized for the most common cases where the number of selectors is small (most commonly 1 or 2). It also should be more memory efficient for medium sized and large applications which have many style names defined in various CSS files.

Due to the optimization, the concept of StyleClass, which was only introduced to assign a fixed bit index for each unique style class name encountered, is no longer needed. This is because style classes are no longer stored in a BitSet which required a fixed index per encountered style class.

The performance improvements are the result of several factors:

  • Less memory use when only very few style class names are used in selectors and styles from a large pool of potential styles (a BitSet for potentially 1000 different style names needed 1000 bits (worst case) as it was not sparse).
  • Specialized sets for small number of elements (0, 1, 2, 3-9 and 10+)
  • Specialized sets are append only (reduces code paths) and can be made read only without requiring a wrapper
  • Iterator creation is avoided when doing containsAll check thanks to the inverse function isSuperSetOf
  • Avoids making a copy of the list of style class names to compare (to convert them to StyleClass and put them into a Set) -- this copy could not be cached and was always discarded immediately after...

The overall performance was tested using the JFXCentral application which displays about 800 nodes on its start page with about 1000 styles in various style sheets (and which allows to refresh this page easily).

On JavaFX 20, the fastest refresh speed was 121 ms on my machine. With the improvements in this PR, the fastest refresh had become 89 ms. The speed improvement is the result of a 30% faster Scene#doCSSPass, which takes up the bulk of the time to refresh the JFXCentral main page (about 100 ms before vs 70 ms after the change).


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (3 reviews required, with at least 1 Reviewer, 2 Authors)

Issue

  • JDK-8322964: Optimize performance of CSS selector matching (Enhancement - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1316/head:pull/1316
$ git checkout pull/1316

Update a local copy of the PR:
$ git checkout pull/1316
$ git pull https://git.openjdk.org/jfx.git pull/1316/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1316

View PR using the GUI difftool:
$ git pr show -t 1316

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1316.diff

Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jan 4, 2024

👋 Welcome back jhendrikx! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@hjohn hjohn changed the title Feature/selector performance improvement JDK-8322964 Optimize performance of CSS selector matching Jan 4, 2024
@hjohn hjohn marked this pull request as ready for review January 4, 2024 12:27
@openjdk openjdk bot added the rfr Ready for review label Jan 4, 2024
@mlbridge
Copy link

mlbridge bot commented Jan 4, 2024

Copy link
Collaborator

@nlisker nlisker left a comment

Choose a reason for hiding this comment

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

I just scrolled quickly through the changes and left some comments,

If deprecation for removal is part of the PR the consequences will need to be discussed. Are the for-removal classes and methods not widely used?

@kevinrushforth
Copy link
Member

As @nlisker notes, the implications of any deprecation will need to be discussed. In particular, we need to consider what the consequences are for existing applications. If any of them might be used by apps, then simple deprecation (rather than deprecating for removal) might be best.

/reviewers 3
/csr

@openjdk
Copy link

openjdk bot commented Jan 4, 2024

@kevinrushforth
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 3 (with at least 1 Reviewer, 2 Authors).

@openjdk openjdk bot added the csr Need approved CSR to integrate pull request label Jan 4, 2024
@openjdk
Copy link

openjdk bot commented Jan 4, 2024

@kevinrushforth has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@hjohn please create a CSR request for issue JDK-8322964 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

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.

Left a couple inline comments.

@hjohn
Copy link
Collaborator Author

hjohn commented Jan 5, 2024

As @nlisker notes, the implications of any deprecation will need to be discussed. In particular, we need to consider what the consequences are for existing applications. If any of them might be used by apps, then simple deprecation (rather than deprecating for removal) might be best.

The methods cannot be reached easily even though they are public. It would involve creating a Selector using Selector#createSelector, and then casting it to SimpleSelector to get access to its public methods that are not inherited from Selector. SimpleSelector itself does not have a public constructor, and the type SimpleSelector is not returned anywhere directly (it is always just Selector in the public API).

The only use of one of the deprecated methods is to allow access for the internal class SelectorPartitioning which indeed does an instanceof to reach it. The other method is not used anywhere, and seems to have been an attempt to make the selectors available in the same form as you'd find them on nodes Styleable#getStyleClass (which is a List), but is hard to reach (probably an oversight that it got exposed in the final API).

Google searches turn up no hits in 3rd party code for either of these.

I've posted on the mailing list to discuss it further.

@mlbridge
Copy link

mlbridge bot commented Jan 5, 2024

Mailing list message from Dirk Lemmermann on openjfx-dev:

I truly love the fact that you guys are starting to use JFXCentral for benchmarking / testing :-) If anyone encounters things bad for performance unrelated to JavaFX itself but caused by JFXCentral then please let me know.

Dirk

@mlbridge
Copy link

mlbridge bot commented Jan 6, 2024

Mailing list message from John Hendrikx on openjfx-dev:

It's a very nice application, I had never seen it before.? As the
original bug was reported against it that caused a performance
regression, I started using it to also test a potential performance
improvement.? Although I think it does a back-end call every time I
"refresh", so I might have been hitting your servers a bit during that
period.

I'll let you know if I discover that's off.? Only thing I did was
disable a piece of code that was causing a stack trace to be logged each
time (a bug in one of the components, fx tray icon, I think).

--John

On 04/01/2024 22:49, Dirk Lemmermann wrote:

I truly love the fact that you guys are starting to use JFXCentral for benchmarking / testing :-) If anyone encounters things bad for performance unrelated to JavaFX itself but caused by JFXCentral then please let me know.

Dirk

@kevinrushforth kevinrushforth self-requested a review January 6, 2024 20:19
public static <T> FixedCapacitySet<T> of(int maximumCapacity) {
return maximumCapacity == 0 ? empty()
: maximumCapacity == 1 ? new Single<>()
: maximumCapacity == 2 ? new Duo<>()
Copy link
Member

Choose a reason for hiding this comment

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

I can confirm that most of the code in my application uses 1-2 styleclasses.
Since all controls have one styleclass by default (some even more, think about TextInputControl with text-input -> TextField with text-field), does it make sense to also implement a specialized Triple class?

We also often add 2 more styleclasses, so 3 style classes seems like a usecase that happens quite often.
I also can confirm that 4 styleclasses or even more are very rare.
I only found one: (.label + 3 added from us)
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for having a look, it's good to have some more use cases from other applications! The sets are used both for the style classes on Nodes but also the set of style classes in CSS selectors (multiple style classes in selectors are even more rare I think than multiple style classes on Nodes).

I think from a performance perspective, Duo and Hashless are likely very closely matched, but I can do some testing in that area. The reason I say this is that I only got a very minor boost from including Duo, but kept it because it is a bit more memory efficient.

If you want, I'm also curious about how many styles are used in your application (you can see this by looking at the size of StyleClassSet#styleClasses after all styles are loaded).

Copy link
Member

Choose a reason for hiding this comment

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

I see, thanks for checking that out.

I checked the application, here is the result:
After starting the app:
image

After using it a little bit:
image


I also checked out Scenebuilder after using it a little bit:
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, it looks like a bit less classes are in use when compared to JFXCentral (which has about 1000), but still quite a lot. With BitSet that means if style classes are referenced that happen to have a high bit set, it creates an array to hold 400 bits (using 8 longs). When few style classes are active, the sets provided by FixedCapacitySet will be often smaller and (due to the other optimizations as well) faster. 8 longs is sufficient to hold 8 or 16 style name references depending on the JVM (32/64 bit, compressed pointers y/n).

@bridgekeeper
Copy link

bridgekeeper bot commented Feb 12, 2024

@hjohn This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@hjohn
Copy link
Collaborator Author

hjohn commented Mar 11, 2024

I think this PR now no longer needs a CSR.

Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle 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 to me.

how do we undo the csr command?

@hjohn
Copy link
Collaborator Author

hjohn commented Mar 11, 2024

I think its /csroff or something...

@hjohn
Copy link
Collaborator Author

hjohn commented Mar 11, 2024

/csroff

@openjdk
Copy link

openjdk bot commented Mar 11, 2024

@hjohn Unknown command csroff - for a list of valid commands use /help.

@hjohn
Copy link
Collaborator Author

hjohn commented Mar 11, 2024

/csr unneeded

@openjdk
Copy link

openjdk bot commented Mar 11, 2024

@hjohn only Reviewers can determine that a CSR is not needed.

@kevinrushforth
Copy link
Member

I was about to reply, but I see that the Skara bot beat me to it. :)

I'll double-check later (or Andy can), and if there are no public API implications, issue the /csr unneeded command.

@andy-goryachev-oracle
Copy link
Contributor

@hjohn is right - no public API changes remain in this PR.

@andy-goryachev-oracle
Copy link
Contributor

/csr unneeded

@openjdk openjdk bot removed the csr Need approved CSR to integrate pull request label Mar 11, 2024
@openjdk
Copy link

openjdk bot commented Mar 11, 2024

@andy-goryachev-oracle determined that a CSR request is not needed for this pull request.

@openjdk
Copy link

openjdk bot commented Mar 13, 2024

@hjohn This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8322964: Optimize performance of CSS selector matching

Reviewed-by: kcr, angorya, mstrauss, mhanl

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 89 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@kevinrushforth kevinrushforth self-requested a review March 26, 2024 18:27
@openjdk openjdk bot changed the title JDK-8322964 Optimize performance of CSS selector matching 8322964: Optimize performance of CSS selector matching Apr 10, 2024
@bridgekeeper
Copy link

bridgekeeper bot commented Apr 10, 2024

@hjohn This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@hjohn
Copy link
Collaborator Author

hjohn commented Apr 21, 2024

I think this is ready to be integrated. Let me know if there is anything still missing.

Copy link
Collaborator

@mstr2 mstr2 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 to me.


/*
* Copied from removed StyleClassSet to give StyleClasses a fixed index when
* first encountered. No longer needed once StyleClass is removed.
Copy link
Member

Choose a reason for hiding this comment

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

Is this a possible idea for the future?

Copy link
Collaborator Author

@hjohn hjohn May 24, 2024

Choose a reason for hiding this comment

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

Yes, once this class is moved to private API (see #1333) then this method can be removed. The SimpleSelector class is already deprecated for removal since 23.

The method is almost impossible to reach (you'd have to cast to SimpleSelector), but removing it as part of this PR would have required a CSR making this harder to integrate.

Copy link
Member

@Maran23 Maran23 left a comment

Choose a reason for hiding this comment

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

Code looks good to me. As mentioned above, I tested it already and everything was good, so I'm certain that there is no regression here.

I'm generally not a big fan of 'reimplementing' Collections, but I can see why it is needed here and it also makes sense, especially for something like CSS which needs to be fast.
Something I saw as well in Swing (just check the ArrayTable class 😄 ).

I wonder if we may want to add some tests for the FixedCapacitySet?

@openjdk openjdk bot added the ready Ready to be integrated label May 23, 2024
@kevinrushforth
Copy link
Member

I'm in the process of testing this. I'll do that and report back.

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.

Code looks good. Testing looks good.

@hjohn
Copy link
Collaborator Author

hjohn commented May 24, 2024

Code looks good to me. As mentioned above, I tested it already and everything was good, so I'm certain that there is no regression here.

I'm generally not a big fan of 'reimplementing' Collections, but I can see why it is needed here and it also makes sense, especially for something like CSS which needs to be fast. Something I saw as well in Swing (just check the ArrayTable class 😄 ).

I'm not a fan either, especially because custom collection implementations when referred to by their interface may sneak their way through the various classes and eventually be returned to users. Any deviation from the collection contracts then suddenly is a bug users may encounter. I would have done this with plain HashSet (or Set.of) were it not for the fact that a lot of performance gains were possible due to the limited way FX is using these sets.

I wonder if we may want to add some tests for the FixedCapacitySet?

Yeah, now that it is more likely that this will make it into FX, I will add a small set of unit tests for this class.

@kevinrushforth
Copy link
Member

I wonder if we may want to add some tests for the FixedCapacitySet?

Yeah, now that it is more likely that this will make it into FX, I will add a small set of unit tests for this class.

Since this PR is ready to integrate, I think it would be fine to file a new test bug for the additional tests if you like. If you prefer to add the new tests now, that's fine, too (we can re-review it).

@hjohn
Copy link
Collaborator Author

hjohn commented May 24, 2024

I wonder if we may want to add some tests for the FixedCapacitySet?

Yeah, now that it is more likely that this will make it into FX, I will add a small set of unit tests for this class.

Since this PR is ready to integrate, I think it would be fine to file a new test bug for the additional tests if you like. If you prefer to add the new tests now, that's fine, too (we can re-review it).

I'm fine integrating this as-is and adding a test soon after. I will leave this over the weekend to give others time to review.

Also some clarification on the contributing rules: "all Reviewers who have requested the chance to review have done so" -- does the indication at the top right of the PR count towards this or should it be a comment? :) In the first case, @nlisker and @arapte, please indicate if you wish to review this still.

@nlisker
Copy link
Collaborator

nlisker commented May 24, 2024

The code looks good. I didn't test it, but I'm fine with integrating.

@tsayao
Copy link
Collaborator

tsayao commented May 24, 2024

I built it and tested on a retail self-checkout app that uses a lot of css. All good.

@kevinrushforth
Copy link
Member

Also some clarification on the contributing rules: "all Reviewers who have requested the chance to review have done so" -- does the indication at the top right of the PR count towards this or should it be a comment? :) In the first case, @nlisker and @arapte, please indicate if you wish to review this still.

If someone wants you to wait for them, they should make it clear by adding a comment. Also if someone has given substantive feedback, but hasn't (re)approved, it's good to give them a change to review.

@arapte can add a comment if he wants to review, otherwise go ahead and integrate on Monday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready Ready to be integrated rfr Ready for review
7 participants