Performance Improvement Techniques for JavaFX Applications
- Skill Level Intermediate
- Product JavaFX
- Key Features Performance
- Last Updated February 2010
This article contains a collection of recommendations and tips aimed at improving the performance of your JavaFX applications.
Turning Off Antialiasing Unless It Is Really Needed
Antialiasing is a technique used for smoothing the edges of curved lines and eliminating distortions of graphical objects to improve the visual appeal of the interface. In JavaFX Script programming language, antialiasing is applied by default to all graphical shapes, which means that the smooth instance variable is set to true.
However, the use of antialiasing has a side effect, which appears in a more complicated rendering. For this reason, you should turn off antialiasing unless it is really needed.
For example, you do not need to apply antialiasing for rectangles unless they are rotated. In particular, smoothing is absolutely not needed when a rectangle is used to limit visibility.
Circle {
clip: Rectangle {
smooth: false
}
}
Work With Images: Avoid Duplication
Look at the code for creating an Image object.
image: Image {
url: "http://my.server.com/image.png"
}
When the Image object is created, it invokes the following actions:
- Loading a file
- Allocating memory for the raster image
- Decoding the image into the raster format
Depending on the size and location of the image, creating extra copies of the image might significantly diminish your application performance. You are strongly encouraged to avoid duplicating.
If the same image is used in several places, create a single Image object and further reuse it in different ImageView instances as shown in the following code.
var img = Image {
url: "http://my.server.com/image.png"
}
for (i in [1..count]) ImageView {
image: img
}
When used improperly, data binding is another typical case that leads to unnecessary cloning of images. Look at the following code and try to think of ways to improve it.
var node: Node;
Group{
content: bind [
node
ImageView{
image: Image {
url: "http://my.server.com/image.png"
}
}
]
}
As you can see, this code loads an image on each change of the node object. To improve the code, have the image loaded only once.
var node: Node;
var img: Image {
url: "http://my.server.com/image.png"
}
Group{
content: bind [
node
ImageView{
image: img
}
]
}
Work With Images: Save on Memory
For most raster images, 4 bytes are used to set the color for each point. This means that a 2-MB file with a 6-megapixel JPEG photo, stored as a raster image, occupies about 24MB of the main memory.
If you do not need to render the image in the full resolution of 2800x2100, but in 800x600 resolution, you can specify the expected dimensions on the image loading. Thus, by adding a couple lines of code you can significantly save on memory footprint (a raster image of 800x600 occupies about 2MB).
Similarly, if you operate with a collection of images while the interface mostly uses image thumbnails, it is a good practice to create reduced raster copies to be used as thumbnails and postpone creation of full images until they are actually needed.
Image{
width: 150
height: 100
url: "http://my.server.com/image.png"
}
Animation: Avoid the Redundant Work
Most modern monitors perform repainting with a frequency of no more than 60 times per second, which is quite enough, as the human eye can recognize only 24 frames per second. For this reason, the more frequent animation is considered a waste of computing resources.
JavaFX Script enables developers to optimize repainting code and avoid redundant work. However, you cannot define programmatically which part of your JavaFX code should not be executed. For example, if your application contains the Timeline object with a frequency of 1/1000 second, which only prepares the scenes to be visualized, then only 6 percent of the time spent on its processing will be a good use of resources!
Avoid unnecessary frequency of animation. This will help to save computing resources for the more important tasks.
def clip = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames:
KeyFrame {
time: 40ms // 25 fps
action: function () : Void {...}
}
}
You can reduce CPU usage by decreasing the Timeline's framerate variable. This parameter defines the number of times a timeline recalculates the interpolating values. By default the framerate is 60 fps, which might be excessive as 30 fps is a sufficient rate with almost no visible difference. For example, imagine a picture that moves slowly in the background. A good guideline is to set the frame rate to a multiple of the monitor refresh rate, such as 15, 30, or 60.
var t1 = Timeline {
framerate: 15
repeatCount : Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) { coordX => 0 ; coordY => 450; },
at (10s) { coordX => 450 ; coordY => 0; }
]
};
Groups: Applying Effects
You can reduce the runtime footprint of your application when you use effects on large groups. You can apply effects either to the entire group or each node. When nodes do not intersect, both techniques give the same visual appearance. However, the latter technique can lead to a smaller runtime footprint.
When you apply an effect to a large group, a rather large intermediate buffer needs to be allocated by the runtime to render the group. If the effect is instead applied to each node then the runtime can reuse a smaller buffer.
The first code example illustrates the DropShadow effect applied to the entire group.
scene: Scene{
content:
VBox {
content: for (i in [1..100])
HBox {
content: for (j in [1..100])
Rectangle{
width: 100 height: 100 fill: Color.RED
}
}
effect: DropShadow {}
}
}
scene: Scene{
content:
VBox {
content: for (i in [1..100])
HBox {
content: for (j in [1..100])
Rectangle{
width: 100 height: 100 fill: Color.RED
effect: DropShadow {}
}
}
}
}
Cache Complex Objects
The JavaFX Script programming language enables you to effectively create scenes from simple graphic elements with applied transformations and effects. For example, the following code creates two complex objects: a circle with the lighting effect and a background element consisting of several rectangular bricks.
var ball : Circle = Circle {
centerX: 250 centerY: 250 radius:100
fill: Color.GREEN
stroke: Color.YELLOW
effect: Lighting {}
}
var brickBackground = Group {
for(i in [0..25]) {
for (j in [0..25]) {
Group {
content:
for (k in [0..7]) {
Rectangle {
x: i * 20 + k
y: j * 20 + k
width: 20 - 2 * k
height: 20 - 2 * k
fill: Color.BLUE
stroke: Color.RED
effect: Lighting {}
}
}
}
}
}
}
}
JavaFX Script enables you to easily animate the ball object against the background. (For more information about animation, see Creating Animated Objects.)
However, this code is not optimal. It is important to understand that complex objects are by default repainted over from their constituent pieces. Repainting of complex objects that consist of a large amount of elements with rich effects might be quite an expensive operation.
In this example, repainting is a waste of resources because both the ball and brickBackground objects always look the same.
The correct approach is to apply caching to the static objects, that is, to set the cache attribute to true. The JavaFX runtime caches the complex object in the interim image and uses this image for repeated repainting.
var ball : Circle = Circle {
centerX: 250 centerY: 250 radius:100
fill: Color.GREEN
stroke: Color.YELLOW
effect: Lighting {}
cache: true
}
Note that caching does not work for some transformations such as rotation. If you need to render images that are always rotated, use your favorite graphical tool to create rotated images and use them during runtime.
Define Variables With def Instead of var
Whenever you need to define a variable whose value is set once when the object is created and never changed, define the variable by using the def keyword. Make the variable script-private as much as possible to allow compiler optimizations.
Avoid Deep-Nested Structures
When you build the graphical scene of your application, remember that making too deep-nested structures might impede the overall performance. The general recommendation is to keep the scene graph as small as possible. Whenever you need to hide some elements, remove them from the scene graph instead of setting their visible variable to false.
Avoid Unnecessary Bindings
Data binding is a powerful feature of the JavaFX Script language, which enables the developer to define the relationship between the two variables. Whenever one variable changes, another variable is automatically updated. However, make sure that you use binding intentionally, as this is quite an expensive operation. Try to avoid unnecessary bindings, such as binding to variables whose values do not change.
Avoid Using Strokes
If you want to change the color of text or draw a filled rectangle with a border, the preferred way to do that is to use the fill variable rather than the stroke variable. It is faster to render two filled rectangles by placing one rectangle on top of the other.
Related Links
- Technical Tip: How to Improve JavaFX Application Startup Time
- Technical Tip: Running Time-Consuming Operations in the Background Mode
- Article: JavaFX Mobile Applications Best Practices for Improving Performance
- Article: Improving Media Performance
We welcome your participation in our community. Please keep your comments civil and on point. You may optionally provide your email address to be notified of repliesyour information is not used for any other purpose. By submitting a comment, you agree to these Terms of Use.
Irina Fedortsova
Technical Writer, Sun Microsystems