JavaFX 1.2.1 is out, fixes binding leaks and performance
Sun just released the first maintenance update for JavaFX 1.2. This release brings mostly a batch of important javafxc fixes, that I dissect in this blog...
Java programmers are used to the fact that compilation of Java source code is a relatively straightforward process, because the Java language has a simple mapping to the Java bytecode. So javac is a trivial compiler, at least in the code generation phase. (Other phases may be more complex – in particular, type-checking has become significantly complex after Java5; but due to erasure, even this basically doesn’t impact code generation.) There haven’t been many code generation bugs over the lifetime of javac, and there are no significant differences in code quality between releases or between javac and other independent compilers (like ECJ). This is different for example from C/C++, where different compilers may produce code of wildly distinct quality.
Of course, the performance and correctness of your code can still vary between JVMs, but that would happen in the bytecode-to-native translation, performed by the JIT compiler. So Java compilation is not really different from C/C++, it’s just split in two layers where all the hard work goes in the lower layer of JIT compilation.
Now with JavaFX, we are closer to C/C++ compilers than javac was. The JavaFX Script language is not a near-1-to-1 mapping of the Java bytecode spec. Quite the opposite, this language – although very similar to Java – contains a few features that require significant extra intelligence to be mapped to bytecode. In ascending order of complexity (as I estimate it):
No surprise that the javafxc compiler is still distant from the standards long established by javac, i.e., to reliably generate bytecode that contains no error and is as efficient as physically possible on the current JVM/bytecode spec.
Now you may think that javafxc is still immature, and this is not really wrong, but reality is more complex: like most higher-level languages, many language features depend on runtime support. Code produced by javafxc doesn’t run on top of a plain JRE even if you could avoid explicit use of any javafx.* APIs; it depends on a significant runtime library (javafxrt.jar: ~1,1Mb in the SDK). So, part of the issues you may have with the JavaFX Script language are proper compiler problems, but others may be caused by bugs or inefficiencies in runtime classes.
When a bug ix fixed in javac, you can just recompile your sources and you get bytecode that’s faster or more correct, even when executed in older JREs. But this rule doesn’t always work for javafxc; first, because some issues may live in the runtime; second, because the JavaFX platform is not yet mature enough to have a stable ABI. Maintenance releases keep at least backwards compatibility, so applications compiled by javafxc 1.2.1 will run on 1.2.0 runtimes. But the downside, that comes from the split compiler/runtime implementation of the language, is that the range of bugfixes that can be delivered in a maintenance update is limited to those not requiring ABI-incompatible changes.
A stable ABI doesn’t have to be immutable, or to provide forward compatibility (allowing newer apps to run on older RT). The ABI’s public interfaces can grow, they only can’t remove or change public interfaces present in previous versions. Even this level of compatibility may seem unimportant because the JRE can manage multiple JavaFX RTs, and new apps that require a new version will fetch it seamlessly on demand… but that’s mostly true for the Desktop profile. Hopefully JavaFX Mobile could support OTA auto-update, and even JavaFX TV might be updateable; but I’m not holding my breath. It’s more likely, at least in some devices, that such updates would be supported only through firmware updates, which doesn’t qualify as user-friendly / seamless. These profiles are yet in development stage so I am just guessing, but as soon as they start shipping in millions of devices, developers will demand stable evolution of javafxc’s ABI from that point forward.
Well, but I digress. What’s new and improved in JavaFX 1.2.1 (codename LafayettePark for javafxc)? The official Release Notes has a broken link that gives you the fixes for JavaFX 1.0.1, so just use the Release Notes feature from the JIRA: JavaFX Script, Runtime. Many interesting javafxc issues, including:
- Fixes to memory leaks, all related to binding (JFXC-3370, JFXC-3369, JFXC-3337, JFXC-3290).
- Slacker Binding optimizations (JFXC-3247, JFXC-3236). These seem to be the “low-hanging fruit” of a general Slacker Binding feature (umbrella: JFXC-3235) which remaining items are scheduled for SoMa (next major version). “The idea of Slacker Binding is that, for side-effect free binds, unless a Location is externally required, in-line code is used instead of Locations.” In other words, the generated code is much more efficient (both memory and execution time) for special cases where not all functionality of binding is actually used.
- Another similar umbrella item is JFXC-3055: Remove intermediate Locations from bind translation. In this case most fixes were delivered in 1.2, now another particular case is optimized in 1.2.1 (JFXC-3060) and there’s just one to go, probably for SoMa (JFXC-3059). These optimizations are similar to those in Slacker Binding, but apparently even better, wiping out it significant generated code.
- More assorted binding optimizations, like JFXC-3317 (Optimize bound JavaFX var select with mutable selectors), JFXC-3315 (Optimize away bound select processing when selector is immutable), and JFXC-3089 (Collapse on-replace triggers into setter).
If you have some complex JavaFX app that uses lots of binding and you’ve been suffering of weird performance of memory leaking behavior, perhaps now you know why. ;-) And there’s more of the same baking in the JIRA, but 1.2.1 is already close to the point of diminishing returns in optimizations of the current binding support (but read on).
This release also brings a few runtime fixes, the most important IMO being:
- RT-5193 (Direct3D Acceleration does not work with unsigned applications), that could be a major performance issue for some people since hardware acceleration is a night-and-day factor for graphics-heavy apps.
- RT-4959 (General FPS reporting (RT-997) broken in Marina b11): Essential for benchmarking junkies like me.
- RT-4802 (Applets sometime do not respond to user input): Seems to be a rare bug (I’ve never hit it even with tabbed browsing), but pretty severe when it hits.
Overall, this is a more interesting maintenance release than past ones like 1.0.1 or 1.1.1 that mostly fixed major screw-ups of their previous dot-zero releases. JavaFX 1.2.1 seems indeed to be a nice refinement update, while you wait for SoMa (1.5?) with the next batch of major features and improvements.
Binding (and Triggers): the Next Generation
But wait, there’s more! It seems that the new round of binding optimizations in 1.2.1 was just a workaround while the real solution for this problem is not implemented – that will happen in the Lombard release (apparently a minor update planned for a couple months after SoMa). The recently posted bug JFXC-3423 (Umbrella: compiled bind) says it all, but I’ll copy & comment its description here because it teaches a lot about JavaFX Script and javafxc:
The currently bind and on-replace are implemented by building an object graph which represents the components of the bind expression (Locations, BindingExpressions, etc), the on-replace (ChangeListeners), and the relationships between them (dependencies, containment). This, let's call it Location-graph, is them executed - effectively interpretively executing the bind - to update values and maintain dependencies. Within an environment of developing semantics, limited resources, and shifting requirements, the compiler would never have been delivered without this flexible approach.
However, the execution time overhead and footprint of the current implementation is judged to be roughly ten times what is acceptable. Many optimizations have already been applied, and the result is factor of two or more improvement - but there is a long way to go. Some optimizations have been simply making the existing implementation more efficient or handling special cases in a more light-weight manner. [I think this refers to JFXC-3055.] But the most seemingly promising optimizations are those that circumvent the Location-graph, instead implementing variables with bind or on-replace with in-lined code. [Now I think this refers to Slacker Binding, JFXC-3235.] These dynamically inflate to a Location-graph on-demand. In micro-benchmarks these give order of magnitude or more improvements in dynamic footprint. However, in real-world testing, the improvements are only a few percent. The reason is that references to these variables from inflated variables inflate the optimized variables - unraveling almost all optimized variables.
The proposal is to move to compiling bind and on-replace into bytecode. Like the in-lining mentioned above this means compiling the value computation into generated code, but, more significantly, the dependency calculation and state needed by that calculation must be generated as well. As a result, Locations, and their supporting structures (BindingExpressions, ChangeListeners, etc) are no longer used, and would be removed. This, of course, saves the footprint used by these structures, but also saves the execution time to build and interpretively execute them, as well as removing the GC churn as these Location-graphs are built and torn-down.
Executive Summary: If even after 1.2.1 you have complex JavaFX apps that use binding too heavily and experience an important performance hit from that, don’t despair, the cavalry is coming. The good news is that we’re always finding that JavaFX’s implementation still has plenty room to improve. In the runtime, SoMa will debut a big revamp of the Scene Graph, even bigger than 1.2: the Prism engine (mostly not public in the JIRA); RT-5252 (Support 3D perspective transforms for existing 2D JavaFX objects); RT-5474 (Scene graph optimizations), and many more. The bad news is that, if you tend to see the glass as half-empty, these pending improvements mean that even JavaFX 1.2.1 is not yet the *really* *hot* release that we wished to have since Dec 2008. The same logic applies to the JRE: 6u10 was a Giant Step for ManKind, and 6u14 even better; but now we’re waiting for 6u18 (to ship in tandem with SoMa) with another batch of important optimizations for loading time and general JPI/JAWS behavior. Not to mention JDK 7 with promising fundamental improvements like Jigsaw (enabling even better loading time, footprint and JavaKernel-like installation), and XRender (potential improvements for any Java graphics over X11).