Wednesday, March 23, 2011

Slim down...

Today i would like to talk about some experiments that i made to reduce the memory footprint of the steelseries components.
As you could read in the first post on this blog i mentioned the problem with the what i called "Layer Technique", where you split your component in different layers. With this technique you create an image for each layer of the component, in my case this means i create images for the frame, background, tickmarks, title, lcd background, pointer, knobs and foreground. The memory footprint of a typical image of a radial component in the steelseries could then be calculated as follows:

The default size of a radial component is 200x200 pixels and i'm using an alpha channel which results in a memory footprint of one image of:

200 x 200 x 4 byte = 160 000 bytes 
(4 byte = 1 byte for red, 1 byte for green, 1 byte for blue and 1 byte for alpha)

This means that a standard radial component in it's default visualization (containing a frame, background, title, tickmarks, pointer, knobs and foreground) will have a memory footprint (only of the images) of: 

7 x 160 000 bytes = 1 120 000 bytes = 1.07 Mb

That seems to be ok even if it's a lot of memory just for the visualization but in these days computers have lots of ram and powerful processors so it should not be a problem.
But if you imagine a huge display showing 20 radial components of a size of 300x300 pixels it could get up to:

20 x 300 x 300 x 4 byte x 7 = 50 400 000 bytes = 48.07 Mb

You might want to say "come on...it's only 50 megabytes, where is the problem" and you might be right but to me it looks too much...which means i had to slim down the memory footprint of the components.

I faced the same problem in the canvas version of the steelseries components and i used the same optimization in the swing version, i combine layers in as less images as possible.
This means i combine the frame, background, title, tickmarks and lcd background in one image and the knob and foreground in another image. Well it's not a huge improvement but it feels better. To give you an idea how i realized it let me show you some code...

Old version: 
public BufferedImage create_FRAME_Image(int size)
{
    GraphicsConfiguration GFX_CONF = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
    BufferedImage image = GFX_CONF.createCompatibleImage(size, size, TRANSLUCENT);
    Graphics2D g2 = image.createGraphics();
    ...
    // Do some fancy drawing here
    ...
    g2.dispose();
    return frameImage;
}

New version:
public BufferedImage create_Frame_Image(int size, BufferedImage image)
{
    if (image == null)
    {
        GraphicsConfiguration GFX_CONF = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        BufferedImage image = GFX_CONF.createCompatibleImage(size, size, TRANSLUCENT);
    }
    Graphics2D g2 = image.createGraphics();
    ...
    // Do some fancy drawing here
    ...
    g2.dispose();
    return image;
}

As you can see the new version takes an image as parameter and if this parameter is not null it will draw the fancy painting code on the given image and returns it again. With this technique you could create an image in the component and pass it to all methods that you would like to add to the image and you will end up with one image only, instead of many images.
The drawback of this method is that you have to create the whole background image each time you change one of the images (e.g. the backgroundcolor of the gauge) but it will reduce the memory footprint.

Another modification i made was again located in the image creation methods. Because the image creation methods will be called at the time of initialization (before the component is really realized) with a size of 0 they have to return something. In the former release i returned an empty image of the default size which means 200 x 200 pixels. But this image was never used because once the component was realized, the image was created again with the default size and the old image was thrown away. By using VisualVM i was able to see a peak in memory usage directly after initialization of the components which was huge. In the current release i return an empty image of the size 1 x 1 pixel which again reduced the memory footprint a bit.

Like i mentioned in my very first post there are more areas of potential "weight loss", e.g. the pointer image could be only of the size of the pointer instead of the full image size etc. 

I'm not sure if this was the right approach but it works and i would love to hear other ideas on how to reduce the memory footprint...

The next release of the SteelSeries library (3.8) will contain all these modifications and will be available soon...so stay tuned and keep coding...

2 comments:

  1. As you mention, saving on memory usage comes at the price of performance. I typically sacrifice memory before I'll sacrifice performance because memory feels like more of an implementation detail, while performance seems like it ultimately benefits the user the most.

    When it comes down to it though, it probably doesn't matter much, since my guess is that drawing these gauges is super fast.

    Just my personal thoughts.
    -Ken

    ReplyDelete
  2. Hi Ken,
    I have to agree with your thoughts in the way that i usually also prefer performance over memory. In this case the performance issue happens only at the initialization which looked as an acceptable compromise between both approaches to me.
    Thanx for your comment and cheers,

    Gerrit

    ReplyDelete