Skip to main content

JavaFX: First programming (and performance) impressions

Posted by opinali on January 16, 2009 at 6:06 AM PST

To learn JavaFX, I used my favorite method of playing with other people's code. I picked JavaFX Balls: the JavaFX version of the Bubblemark RIA benchmark. The original Java code (for Swing) was written by Richard Blair, and later optimized, enhanced and ported by several other contributors. Alexei Gravilov produced a port for JavaFX Preview, but that didn't work on the 1.0 FCS release and Alexei didn't have the time for this update... so, I took up the baton. It was a very fun experience as I not only fixed everything for 1.0 compatibility, but tried to enhance the code in several ways.

UPDATED: I removed this old version of JavaFX Balls, mostly to make sure nobody will use it anymore as a benchmark because it suffers from JavaFX 1.0's performance. Please check the latest version, for JavaFX 1.3.

Follows a (hopefully interesting) account of my experience and findings, including investigation of JavaFX's performance.

From Preview to FCS

I started by doing all changes recommended by the Stephen Chin's guide, so I won't repeat this information here but just comment additional issues.

  • Support for Swing menus was gone, so I created a new toolbar. With 128 balls, the ball moving algorithm "spills" some balls off-limits, rendering those on top of the toolbar! So I added clipping to avoid that. (The original code substituted the standard 128-ball options for a 100-ball option, perhaps to minimize the spilling - I fixed that too, because it's a benchmark suite and all versions must be functionally identical.)
  • The code uses an internal Settings class to set JavaFX's PULSE_DURATION property. (The equivalent JVM argument is -Dcom.sun.scenario.animation.pulse.) This property caps the maximum FPS for animation; I changed it from 200 to 1000. A 1000fps rate is not useful in the real world (no monitors that good!), but in a benchmark you don't want any capping: more is always better. ;-) For the same reason I reduced the animation's single KeyFrame time tag from 5ms (also caps at 1000/5 = 200fps) to 0ms (explained later).
  • The code that creates a vector ball, once compiling, didn't generate the correct shape. I fixed that with forcing stroke:null on a Path object; I guess this property's default changed from the Preview release. Expect more obscure porting issues like this if you've written some code with the Preview...

At this point I had a working program again. (Bug report: if I set the PULSE_DURATION to any value higher than 1000, the runtime bombs with a divide-by-zero ArithmeticException.) Then I went to enhance it, attempting to make best-practice use of the JavaFX language:

  • Used def (= immutable variable) instead of var (mutable) whenever possible. I do that even in Java (using final everywhere I can), but JavaFX may benefit even more of this optimization because its sophisticated binding mechanism only applies to var data.
  • Deleted static type declarations everywhere I could, to check how good is JavaFX's type inference. It's good; can infer types for initialized variables and also function returns, but not for function arguments.
  • Used static import like java.lang.Math.*. JavaFX's syntax is simpler than Java 5, it doesn't require static.
  • Explored JavaFX's powerful for construct, e.g. to create a row of similar buttons without code duplication, and to write a nested loop with a single for.
  • Made further use of binding & triggers, check the start/stop actions. This resulted in a proper lifecycle, easier to understand.

The last item explores the declarative side of JavaFX, that is quite cool, so let's look at it in some detail. The program has a _is_runing status variable that tells whether the animation is started or stopped:

    var _is_running = false on replace {
        if (_is_running) {
            fps = "-- fps";
            timeline.play();
            fpsTimer.start();
        } else {
            timeline.stop();
            fpsTimer.stop();
        }
    };

When this status var changes, it either starts the animation (a JavaFX Timeline) and a FPS listener (a Swing Timer). But there's more. Here is the source code for the button that controls the Stop/Start actions:

    SwingButton {
        text: bind if (test._is_running) "Stop" else "Start"
        action: function() { test._is_running = not test._is_running; }
    }

This single button cycles its label and function, depending on the status of _is_running, now thanks to JavaFX's bind mechanism. It couldn't be simpler, I just flip the boolean status variable. In the final touch, there's how I start the animation in the end of JavaFXBalls.fx:

test._is_running = true;

The original code invoked a start() method here; I first made that method private, and later refactored it into _is_running's on replace trigger. The new code is better encapsulated, relying more in the declarative paradigm. The public interface is a set of state variables, so when I want for example to put the program in "running" state, I just say "running = true" instead of saying "start running". It's not just a matter of syntax; the former mechanism is more expressive and powerful as I can attach dependant behaviors to the same state variable, I can program in a FSM-like style without extra gearing for that.

The FXD vector graphics format

Now the major change: I refactored the vector-ball creation code into a FXD file. Easy job, because FXD is actually just plain JavaFX Script code - with some restrictions: purely declarative, a Group root, only compile-time constants as property values. (Code is data: old LISP school...) In other words, I could replace a lot of code with a resource. This is important, because code size is a popular metrics of EoD. The image still needs to be created, but now a designer can to do that in an illustration package in a couple minutes and use the JavaFX Production Suite to convert it into FXD or FXZ; and keeping it in that format facilitates the entire development cycle.

But in order to load the FXD, I'd have to use a FXDLoader API that's only available in an extension package. This would add a 41Kb dependency (in .pack.gz format!) to the program. That sucks, because my new JavaFX Balls would be just 12Kb (also packed) otherwise. So I re-refactored the FXD back to code, but now with a dedicated class which structure is trivial to convert to and from FXD (see its comments). (I even envisioned a tool that would automatically convert between .fxd and .class... an IDE plugin could integrate this to the project's build and classpath.) In this class, the image is created by a function, here is the code:

//@version 1.0 
package javafxballs;

import javafx.scene.Group;
...

public function data (): Group {
Group { // Standard FXD content begins here
    content: [
        ...
    ]
} // Standard FXD content ends here
}

I wanted to just def the image, but the program uses many of those balls and they would end up sharing the same position. In a final attempt wanted to use the prototype pattern, cloning an initial Group for each animated ball; but I couldn't find a way to clone JavaFX objects - seems like a glaring hole, especially for Scene Graph objects.

Evaluating Performance

I tested the new version in JavaFX 1.0 Update 1, on a Dell Vostro 1400 laptop (T7250 CPU, 2Gb RAM) with a GeForce 8400M GS card with 128Mb. This is a cheap and old configuration for today's laptop standards, and I wanted a grasp of JavaFX's performance on real-world business/consumer hardware, not developer's/gamer's god-boxes.

On this configuration, JavaFX Balls initially delivered ~230fps in the standard 16 balls option. But then I realized that the animation pipeline should be making a pause of up to 1ms after each frame (at least, for frames rendered in less than 1ms), because I had changed the KeyFrame's time tag to 1ms. So, I experimented changing it to zero:

public def timeline = Timeline { 
    repeatCount: Timeline.INDEFINITE autoReverse:false
    keyFrames: KeyFrame {
        time: 0ms canSkip: true
        action: function() {
            for (i in [0 .. _N - 1])
                balls[i].move();

            for (i in [0 .. _N - 1], j in [i + 1 .. _N - 1])
                balls[i].doCollide(balls[j]);

            _frames = _frames + 1;
        }
    }
}

A Timeline with a single KeyFrame at 0ms doesn't make sense in many cases (if you have any interpolation, path, or other activity that assumes a non-zero duration); but it makes sense for JavaFX Balls. So I tested again, and bingo!, 340fps at 16 balls. Save that trick for any performance demo with JavaFX. ;-) This animation rate costs 35% CPU usage (dual-core; or 70% of a single core).

For 1 ball I got ~950fps, with just 15% CPU usage. That's quite a feat, even for a single sprite moving over a white background. The animation has a 500x328x32bpp window (toolbar included), with a few layers of buffering (JavaFX's and also Windows Vista's due to the Aero composition). Just for fun I hacked a test of 0 balls, that delivered 995fps with 0% CPU. (More precisely, a few dozen ms each 10-15 seconds could be observed by the Process Explorer.) This shows that the runtime avoids redundant work, like rendering anything when no Scene Graph state changed. Higher FPS rates are only impossible due to the maximum PULSE_DURATION cap of 1000Hz.

But for 128 balls I got only 20fps, that doesn't look good - even 128 sprites are not challenging even for my modest GPU. Why? The Balls collision algorithm doesn't scale; the number of calls to doCollide() is O(N^2 / 2) for the number of balls (check the action function above). But even completely eliminating that code didn't change the performance in a single FPS. So, the good news is that JavaFX is just as fast as Java in things like arithmetic and method calls: the overhead of 8.192 calls per frame to the doCollide() method was totally insignificant.

The bad news, however, is that there's no excuse for just 20fps with 128 balls. CPU usage was 50% (the program is single-threaded, so that's one core fully saturated). A partial diagnostic: hacking update() to only update the position of a random 10% of the balls, the 128 balls test scored 125fps... so, it seems that the renderer is highly optimized for the common case (in RIAs anyway) of a mostly-stable Scene Graph, with incremental rendering - a "retained mode" approach. But its overhead is too big for mostly-unstable frames with many nodes.

Checking some competitors at the 128 balls test:

  • Flash/Flex is just as bad: 21fps, 83% CPU;
  • Silverlight/CLR is mediocre: 40fps, 82% CPU;
  • Flash/cacheAsBitmap is good: 64fps, 58% CPU;
  • Java/Swing (optimized) is excellent: 163fps, 50% CPU;
  • Java/PulpCore is the winner: 200fps, 50% CPU.

So, in the "scene scalability benchmark", JavaFX is a loser... Java is a champion, just not with JavaFX. I hope Sun moves fast to address the bottleneck that lies inside its Scene Graph, otherwise JavaFX stands no chance in the important action game niche (except for 1970's style games like Space Invaders, with a couple dozen sprites over a scrolling background).

It's worth notice that Flash and Silverlight could make great use of my dual-core system (using way over 50% CPU); they should have some level of multithreading. But at least for Flash, this doesn't translate into good performance.

Overall, I was satisfied with this initial experience. The JavaFX Script language is really nice, as well as its animation frameworks (and I barely touched it, feature-wise). Swing integration was easy, at least for my modest Swing-based toolbar, there's a lot missing in this area (more Swing components; native JavaFX components; higher-level events etc.). Performance starts looking very strong for simple animations and it's certainly decent overall, for anything v1.0; but but it doesn't scale, this needs urgent attention, hopefully for some JavaFX 1.x release (2.0 would be too late for such an important problem).

UPDATE: The applet was updated to JavaFXBalls 2. The source bundle referenced here is still for the original version.

Related Topics >>