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
8264770: BidirectionalBinding should use InvalidationListener to prevent boxing #454
8264770: BidirectionalBinding should use InvalidationListener to prevent boxing #454
Conversation
👋 Welcome back mstr2! A progress list of the required criteria for merging this PR into |
I don't see this or any similar bug filed in our incident tracker. Did the submission complete to the point where you have an internal tracking number? Is there a measurable benefit in doing this? For example, do you have a benchmark of some sort that shows garbage generation has been reduced or performance has been improved? |
Seems like I forgot to hit the send button on the webform. Here's the tracking number: 9069787. I've used the following manual benchmark, which bidirectionally binds two properties and then produces a billion change notifications.
And these are the results I got (time elapsed, in milliseconds):
So in this synthetic benchmark, the new implementation has a 3x performance improvement compared to the old implementation. |
I see it now. And thanks for providing the benchmark. That's what I was looking for. |
The bug is now visible here: https://bugs.openjdk.java.net/browse/JDK-8264770 |
Webrevs
|
The benchmark might not tell the real story. To test these sorts of performance changes you have to use JMH. There's too much relating to JIT optimizations and JVM startup to be able to rely on the current benchmark. |
While true, my main motivation for this issue was that the original code is wrongly implemented because it claims to do one thing, but doesn't do that thing. So it's not primarily a question of optimization, but of correctness of the implementation. Anyway, here's a comparison benchmark done with JMH:
|
/reviewers 2 |
@kevinrushforth |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks all good to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix looks good, but I noted two places where I think you need to initialize oldValue
.
modules/javafx.base/src/main/java/com/sun/javafx/binding/BidirectionalBinding.java
Show resolved
Hide resolved
modules/javafx.base/src/main/java/com/sun/javafx/binding/BidirectionalBinding.java
Show resolved
Hide resolved
I've added the two missing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good (I note that the test failure on Linux is an unrelated bug that is under evaluation).
@mstr2 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:
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 59 new commits pushed to the
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. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@arapte, @kevinrushforth) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
/integrate |
/sponsor |
@arapte @mstr2 Since your change was applied there have been 59 commits pushed to the
Your commit was automatically rebased without conflicts. Pushed as commit 285a0b6. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
@@ -150,7 +151,7 @@ public static BidirectionalBinding bindNumber(DoubleProperty property1, Property | |||
private static <T extends Number> BidirectionalBinding bindNumber(Property<T> property1, Property<Number> property2) { | |||
checkParameters(property1, property2); | |||
|
|||
final BidirectionalBinding<Number> binding = new TypedNumberBidirectionalBinding<T>(property1, property2); | |||
final BidirectionalBinding binding = new TypedNumberBidirectionalBinding<>(property1, property2); | |||
|
|||
property1.setValue((T)property2.getValue()); | |||
property1.addListener(binding); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About lines 156-158:
property1.setValue(property2.getValue());
property1.addListener(binding);
property2.addListener(binding);
The bindings added do not validate the properties anymore as it is an invalidation listener now instead of a change listener. This doesn't matter for property2
as its getValue
is called which will force its revalidation, but for property1
this is not the case. This small program demonstrates this:
SimpleDoubleProperty p = new SimpleDoubleProperty(2);
InvalidationListener invalidationListener = obs -> System.out.println("Invalidated");
p.addListener(invalidationListener);
p.setValue(3);
p.setValue(4);
The program as expected only prints invalidated
once.
The internal BidirectionalBinding class implements bidirectional bindings for JavaFX properties. The design intent of this class is to provide specializations for primitive value types to prevent boxing conversions (cf. specializations of the Property class with a similar design intent).
However, the primitive BidirectionalBinding implementations do not meet the design goal of preventing boxing conversions, because they implement ChangeListener.
ChangeListener is a generic SAM interface, which makes it impossibe to invoke an implementation of ChangeListener::changed with a primitive value (i.e. any primitive value will be auto-boxed).
The boxing conversion happens, as with all ChangeListeners, at the invocation site (for example, in ExpressionHelper). Since the boxing conversion has already happened by the time any of the BidirectionalBinding implementations is invoked, there's no point in using primitive specializations of BidirectionalBinding after the fact.
This issue can be solved by having BidirectionalBinding implement InvalidationListener instead, which by itself does not incur a boxing conversion. Because bidirectional bindings are eagerly evaluated, the observable behavior remains the same.
I've filed a bug report with the same title.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.java.net/jfx pull/454/head:pull/454
$ git checkout pull/454
Update a local copy of the PR:
$ git checkout pull/454
$ git pull https://git.openjdk.java.net/jfx pull/454/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 454
View PR using the GUI difftool:
$ git pr show -t 454
Using diff file
Download this PR as a diff file:
https://git.openjdk.java.net/jfx/pull/454.diff