Optimization

There are three key factors to considered when you want to optimize your Java ME applications

  • Performance
  • Size

You should delay optimization until the last minute, after you create the main features of our application, but you need to keep in mind all the key factors throughout the development cycle to avoid huge changes at the end.

Performance

Rule number one for performance is “keep it simple”, don’t try to create over complex systems, for your mobile phone, remember people want fast and easy to use applications/games, to use on the move.
With this mind take in account the following issues:

Threads

  • Use only one application thread, avoid multiple threads. Many devices cannot handle too many threads, they simple stop. Network thread is the only exception for creating a new thread
  • Minimized usage of synchronized, is expensive on legacy devices and is very common to create poorly synchronized code. Synchronizing run with paint() and keyPressed() is ok.
  • Avoid using Timer class, an extra thread is created for each one.
  • Create a background thread in startApp() and reuse it.
  • Don’t use Display.callSerially(), is very slow and buggy on many devices. A new thread is created in most implementations
  • Avoid serviceRepaints() on legacy devices when performance become an issue
  • Might need to ensure thread safety (background thread and system thread) if serviceRepaints() is not used

An example for the base structure for you application:

public void startApp() {
  animationThread = new Thread(this);
}

public void run() {
  init();
  while(!exitApp) {
    updateModel(); // your app logic
    repaint();
    serviceRepaint();
    sleep(50); // may choke slow devices if too small
  }
}

System Callbacks

You must handle with care the system callbacks, they are called by the system thread. They must not block and should return as soon as possible to avoid slowing down the VM. A crash can happen if not returning quick enough.
Here is a list with the more common system callbacks:

  • paint
  • keyPressed
  • startApp/pauseApp
  • hideNotify/showNotify
  • MIDlet constructor

Below you have a bad example of a system callback:

public void paint(Graphics g) {
  updateModel();
  drawBackground(g);
  drawForeground(g);
}

public void run() {
  while(!exitApp) {
    repaint();
    serviceRepaints();
    sleep(50);
  }
}

As you may have noticed we are updating our model, an expensive operation, during the system event paint. We should change our code to the following:

public void paint(Graphics g) {
  drawBackground(g);
  drawForeground(g);
}

public void run() {
  while(!exitApp) {
    updateModel(); // better do it here
    repaint();
    serviceRepaints();
    sleep(50);
  }
}

Different Devices - Different Performances

There are huge differences between high end and low end devices, devices can be much faster or slower than you expected, so you cannot assume performance.

If you have your game logic based on frame rate this may result in totally unplayable game. You should instead base your game on actual time instead, like the example below:

public void updateModel() {
  curTime = System.currentTimeMillis();
  elapsedTime = curTime – prevTime;
  // using elapsedTime for your app logic here
  prevTime = curTime;
}

public void run() {
  while(!exitApp) {
    updateModel();
    repaint();
    serviceRepaints();
    sleep(50);
  }
}

Another thing to take in account is the performance of some APIs, some calls are slower than others:

  • drawString(), replace with graphics font
  • drawArc(), Vector, Hashtable, replace with own implementation
  • drawImage(), replace large images with a series of smaller images for some devices

Another thing to handle with care is collision detection, it must be calculated in between frames to avoid problems on low frame rate devices.

Disable certain features on slow device via JAD entry. E.g. if “tree.png” does not exist, don’t draw the tree sprite

I/O

The use of RMS and getResourceAsStream calls is very slow. Take the following consideration when using them:

RMS

  • Read entire record into a buffer
  • Then parse the buffer
  • Similarly, write to a single buffer, then write the buffer to a record

getResourceAsStream()

  • Extremely slow on some devices (as slow as 20 bytes per second)
  • Class loading is much faster on these devices
  • Workaround—store data in separate class files instead of using resource. This may result in slightly longer application load time

General

Here some “Little things” that make big differences:

  • Avoid unnecessary object creation/memory allocation.
  • Reduce, reuse, recycle the object instances you use
  • Strings, don’t do big string concatenations using “+”, use StringBuffer class instead
  • Image, small is good—split large images, create a Image cache
  • Object pooling might work in some cases
  • Loops, avoid unecessary creation/disposal of variables inside loops
  • Use switch-case instead of if-blocks, they are translated to faster java bytecodes
  • Use public variables directly instead of using get/set methods.
  • Set variables to null when you don’t need them anymore
  • Garbage Collector, call frequently and explicitly on some devices. Beware of non-compacting GC
  • Use local variables instead of global variables when you can. Local variables are faster and use less bytecode

Take a look at the following example:

for(y = 0; y < oriHeight; y++) {
  for(x = 0; x < oriWidth / 2; x++) {
    int idx1 = y * oriWidth + x;
    int idx2 = (y + 1) * oriWidth – 1 - x;
    curPixel = buf[idx1];
    buf[idx1] = buf[idx2];
    buf[idx2] = curPixel;
  }
}

If we do the index creation outside of the loops like this:

  int idx1;
  int idx2;
  for(y = 0; y < oriHeight; y++) {
    idx1 = y * oriWidth;
    idx2 = (y + 1) * oriWidth – 1;
    for(x = 0; x < oriWidth / 2; x++) {
      curPixel = buf[idx1];
      buf[idx1] = buf[idx2];
      buf[idx2] = curPixel;
      idx1++;
      idx2--;
    }
  }

It can save a few seconds on a real device!

Size

The size restriction for Java ME applications comes from two sources:

  • Device capability, some devices only accept 64 kb applications!!
  • Operator’s gateway limitation, to avoid customers waiting too long (and pay to much)for an application to download some operators limit the max application size (normally around 300kb)

Device JAR size limit has improved continuously over time, but it is never enough for developers! So here some tips to help you

  • Minimize classes number, avoid OOP (if practical), each class/interface contribute at least 250 bytes of overhead after compression. Can get away with 2 classes: one MIDlet class and one Canvas class.
  • Use an Obfuscator
  • Optimize Images
  • Change ZIP algorithm, JDK’s JAR utility is not optimal. Use open source, freeware or commercial alternatives, where number of passes are configurable, but beware of device compatibility.

Obfuscator

What does it do? It’s original purpose is to make reverse engineering very difficult by:

  • Eliminate packages (i.e. always use default package)
  • Rename method/field names
  • Remove unused code

But is has the nice side effect of creating smaller class files size (and also slightly faster). Some of them also perform bytecode optimization. They can typically can reduce file size by 30–50%
There are very good Open source obfuscators (Proguard, Retroguard) and many other commercial products.

Images

What are the problems?

  • Not compressible
  • Each PNG file contains more of less the same header and footer
  • Flipped images, transformed images on MIDP 2.0 devices are either very slow or not working. Isn’t supported in MIDP 1.0 devices. Must include flipped version of sprites at build time this can double or quadruple the size

Here are the solutions:

  • Use PNG optimizer like OptiPNG or PNGCRUSH
  • Alternatively, do not compress image content - larger PNG sizes before compression but smaller at the end
  • Combine multiple PNGs into a single resource bundle or a large image. It becomes more compressible and we stripped off the header and footer
  • Flipped images
  • Dynamically flip images at run time
  • Potentially a performance/JAR size tradeoff
  • Reduce colour depth (if possible)

6 Responses to “Optimization”


  1. 1 marc

    Hi Sérgio,

    thanks for this nice listing of hints on how to performance-optimize j2me apps.

    I am not quite sure about the following point though:
    “Don’t use Display.callSerially(), is very slow and buggy on many devices. A new thread is created in most implementations”

    Isn’t the idea of callSerially to run in exactly the thread which serves the ui - i.e. the main thread?

    Cheers
    marc

  2. 2 Sérgio

    Hi Marc

    If you read the documentation about this method you will read the following:

    “The callSerially() method may be called from any thread. The call to the run() method will occur independently of the call to callSerially(). In particular, callSerially() will never block waiting for r.run() to return.”

    For this method not to block what most implementation do is to create
    a Thread with the Runnable passed as argument, and return.

    The call for the runnable is made from the main ui thread but the run method is done in another process.

    Cheers

    Sérgio

  3. 3 Ben

    Hi,

    I’m guessing you’re a mobile Java developer of just a few years experience - I feel I must correct some of your misunderstandings.

    Minimise scope - ALWAYS on a JME device, outside is old world thinking! The tiny performance overhead from recreating each time does not make up for loss of performance in the JVM due to less memory, and code visibility/bug finding is also improved. Joshua Bloch also advocates it. Sample chapter from Effective Java:
    http://www.informit.com/content/images/0201310058/samplechapter/ch7generalp.pdf
    http://mindprod.com/jgloss/scope.html
    http://slightlyrandombrokenthoughts.blogspot.com/

    Garbage collector - do NOT call this frequently, this is counter to all good Java advice. It simply wastes processor time.
    Imagine, a manual GC cleans a 2K variable after a method call, when instead the auto GC could have collected 100K in a one swoop and one GC call when memory was actually low. Analysis of what memory needs freeing is what makes GC so slow, not the memory cleanup - it needs to check all object references and scopes.

    Advocating switch over if is trivial - quite simply both can produce the same bytecode. Instead where possible (if the branches are not for method calls), a hashtable lookup should be used instead as it is MUCH faster since no CPU branching operations are needed - beware however, hashtable is synchronized and depending on your program could be slower. (On full Java is it the preferred approach).

    Avoiding OOP is also a bad idea. Having monolithic code is bad for GC, bad for maintenance/code visibility (important for your team) and counter to good Java OOP. Your lecturer should have told you never to write 1000+ line long classes/methods! :)
    In some cases direct variable access is the best thing to do (e.g. protected from an extended class, or a very simple struct like class) , but you should never use PUBLIC access of variables unless they are final static. This is can be the source of many bugs and makes debugging a nightmare as you cannot put a breakpoint in one location. It too is also counter to code OO practice.

    In addition, where possible, don’t cache large objects to save memory - reread them if possible from the RMS.

    Please, even if you delete this post - read the book Effective Java, it’s a real eye opener on how to write efficient clean code by one of the guys who actually helped design Java. Some of the material you have mentioned here is not good practice, and you may have put it in in the (mistaken) belief that it will somehow improve the performance based on hearsay of how Java works. If you need better performance and to write monolithic code use embedded C - but don’t advocate it for a language designed to be OO - it’s especially bad if you work in a team. Having said that - well done in pointing out premature optimization is bad and “keep it simple”, I come across numerous issues daily where the developers write “// optimization - this now….” when we haven’t even ironed out existing bugs, and it is optimization causing them!

    Ben (*Personal Java*/JME/.NET CF developer of 11 years experience - I’ve been commercially developing in Java since 1.0 in 1997)

  4. 4 Sérgio

    Hi Ben

    First of all thanks for your comments. I’m also a experienced Java programmer, coding since 2000, and Java ME since 2002, and yes I already read the Effective Java, a classic book that I recommend for all Java Programmers.

    The tips that i point out in this article come for my experience developing some commercial games during this years, I don’t say you need to use them all in your application, because as you say the majority of them are a tradeoff between size/performance and readability.
    For me ideally we should always develop using good OOP, but sometimes the size, performance constraints and bad JVM implementation of the devices simple don’t allow you to do this.

    Luckily the Java ME devices are getting more and more performance and memory each year (my new N95 has 112 Mb of Ram!!!) and we can allow us to come back to the good practices :).

    But even now, for example Google with the new Android devices recommends some shortcuts, take a look at http://code.google.com/android/toolbox/performance.html

    Cheers

    Sergio

  5. 5 Ben

    Hi Sergio,

    Apologies for my last opening paragraph btw - I only read your About page afterwards! I didn’t want to come across as high and mighty sounding - I just wanted to point out that in my experience of mobile apps I did not find some of the advice to ring true.
    I understand with games a lot of work must be pre-cached if possible because the CPU is slow (I once wrote Tetris for the Z80 powered TI83 calculator and the transforms and rotates were awful so I had to cache). In my line of (boring) work I use C# and mobile databases with hundreds of thousands of records held locally and from experience, even an “intelligent” cache bogs the CPU down more than retrieving the data directly because of the sheer scale of data to be sorted. I guess what I’m trying to say is that for a limited device there is a performance/memory trade off, and if possible, don’t cache unless there are performance problems - games being the exception because they would have performance problems.

    Regards,

    Ben

  6. 6 Anders

    Ben seems to know a lot about standard java programming and optimization, but I doubt he has ever developed anything serious for multiple low-end j2me/javame platforms. It’s a hazzle, and while some of these tips seems strange and not really the “java way” of doing things, they’re actually really helpful.

Leave a Reply