Fork us on GitHub

Performance True Story

Real world performance pitfall tracking and the workaround
Post Image

Performance True Story

We had almost everything ready for the release of the kitchen sink demo this week until one of our fixes broke the build and we couldn’t get everything out in time. It’s disappointing but this means one more week to refine the demo.

During our debugging of the contacts demo that is a part of the new kitchen sink we noticed its performance was sub par. I assumed this was due to the implementation of getAllContacts & that there is nothing to do. While debugging another issue Steve noticed an anomaly during the loading of the contacts.

He then discovered that we are loading the same resource file over and over again for every single contact in the list!

In the new Contacts demo we have a share button for each contact, the code for constructing a ShareButton looks like this:

public ShareButton() {
    setUIID("ShareButton");
    FontImage.setMaterialIcon(this, FontImage.MATERIAL_SHARE);
    addActionListener(this);
    shareServices.addElement(new SMSShare());
    shareServices.addElement(new EmailShare());
    shareServices.addElement(new FacebookShare());
}

This seems reasonable until you realize that the constructors for SMSShare, EmailShare & FacebookShare load the icons for each of those…​

These icons are in a shared resource file that we load and don’t properly cache. The initial workaround was to cache this resource but a better solution was to convert this code:

public SMSShare() {
    super("SMS", Resources.getSystemResource().getImage("sms.png"));
}

Into this code:

public SMSShare() {
    super("SMS", null);
}

@Override
public Image getIcon() {
    Image i = super.getIcon();
    if(i == null) {
        i = Resources.getSystemResource().getImage("sms.png");
        setIcon(i);
    }
    return i;
}

This way the resource uses lazy loading as needed.

This small change boosted the loading performance and probably the general performance due to less memory fragmentation.

The lesson that we should learn every day is to never assume about performance…​

Scroll Performance - Threads aren’t magic

Another performance pitfall in this same demo came during scrolling. Scrolling was janky (uneven/unsmooth) right after loading finished would recover after a couple of minutes.

This relates to the images of the contacts.

To hasten the loading of contacts we load them all without images. We then launch a thread that iterates the contacts and loads an individual image for a contact. Then sets that image to the contact and replaces the placeholder image.

This performed well in the simulator but didn’t do too well even on powerful mobile phones. We assumed this wouldn’t be a problem because we used Util.sleep() to yield CPU time but that wasn’t enough.

Often when we see performance penalty the response is: "move it to a separate thread". The problem is that this separate thread needs to compete for the same system resources and merge its changes back into the EDT. When we perform something intensive we need to make sure that the CPU isn’t needed right now…​

In this and past cases we solved this using a class member indicating the last time a user interacted with the UI. Here we defined:

private long lastScroll;

Then we did this within the background thread

// don't do anything while we are scrolling or animating
long idle = System.currentTimeMillis() - lastScroll;
while(idle < 1500 || contactsDemo.getAnimationManager().isAnimating() || scrollY != contactsDemo.getScrollY()) {
    scrollY = contactsDemo.getScrollY();
    Util.sleep(Math.min(1500, Math.max(100, 2000 - ((int)idle))));
    idle = System.currentTimeMillis() - lastScroll;
}

Notice that we also check if the scroll changes, this allows us to notice cases like the animation of scroll winding down.

All we need to do now is update the lastScroll variable whenever user interaction is in place. This works for user touches:

parentForm.addPointerDraggedListener(e -> lastScroll = System.currentTimeMillis());

This works for general scrolling:

contactsDemo.addScrollListener(new ScrollListener() {
    int initial = -1;
    @Override
    public void scrollChanged(int scrollX, int scrollY, int oldscrollX, int oldscrollY) {
        // scrolling is sensitive on devices...
        if(initial < 0) {
            initial = scrollY;
        }
        lastScroll = System.currentTimeMillis();
        ...
    }
});
Due to technical constraints we can’t use a lambda in this specific case…​

Final Word

Performance is a chase that never ends. Its non-trivial and always changes on device/between devices.

The nice thing about cross platform tools is that once you optimize something on Android this often maps back to iOS etc. giving you a nice cross platform boost.

Some performance tips are generic and you can check them out in our developer guide but performance is basically application specific. We wouldn’t have seen the ShareButton issue since the overhead is so small. But once we used ShareButton in an app where we created hundreds of buttons it became an issue…​

Share this Post:

Posted by Shai Almog

Shai is the co-founder of Codename One. He's been a professional programmer for over 25 years. During that time he has worked with dozens of companies including Sun Microsystems.
For more follow Shai on Twitter & github.