Performance Improvement Techniques for JavaFX Applications

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.

Source Code
Circle {
     clip: Rectangle {
         smooth: false
     }
  } 

Work With Images: Avoid Duplication

Look at the code for creating an Image object.

Source Code
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.

Source 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.

Source Code
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.

Source Code
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.

Source Code
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.

Source Code
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.

Source Code
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.

Source Code
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 {}
        }
} 
The previous code sample can be rewritten to apply the same visual effect to each node in the group as follows.
Source Code
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.

Source Code
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.

Source Code
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

Rate This Article
Discussion

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 replies—your information is not used for any other purpose. By submitting a comment, you agree to these Terms of Use.

 

English
日本語
한국어
简体中文
русский
Português do Brasil