Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Store a color in Java - byte;byte;byte vs. byte[3] vs int

Tags:

java

byte

I need to store a ton of RGB color objects. These are taking up between 8% & 12% of the total memory of my app for some common usages. I presently have it defined as follows:

class MyColor {
byte red;
byte green;
byte blue;
}

I assume that (most) JVMs actually use an int for each of those entries. The easiest alternative is:

class MyColor {
byte [] color = new byte[3];
private static final int red = 0;
private static final int green = 1;
private static final int blue = 2;
}

Will that put the entire array in a single int? Or is it an int[3] under the covers? If the first, this is great. If the second, then the best is:

class MyColor {
int color;
private static final int red_shift = 0;
private static final int green_shift = 8;
private static final int blue_shift = 16;
}

Or is there a better approach?

Update: I will also have a getRed(), setRed(int), ... as the accessors. I just listed the data components of the class to keep it smaller. And size is the critical issue here. The code doesn't spend a lot of time accessing these values so performance is not a big issue.

Update 2: I went and ran this using SizeofUtil (referenced below - thank you). I did this using code as follows:

    protected int create() {
        MyColor[] aa = new MyColor[100000];
        for (int ind=0; ind<100000; ind++)
            aa[ind] = new MyColor2();
        return 2;
    }
}.averageBytes());

And here's where it gets weird. First, if I don't do the for loop, so it only is creating the array (with all values null), then it reports 400016 bytes or 4 bytes/array element. I'm on a 64-bit system so I'm surprised this isn't 800000 (does Java have a 32-bit address space on a 64-bit O/S?).

But then came the weird part. The total numbers with the for loop are:

  • 2800016.0
  • 2600008.0
  • 2800016.0

First surprise, the 2nd approach with byte[3] uses less memory! Is it possible that the JVM, seeing the byte[3] in the declaration, just allocates it inline?

Second, the memory per object is (2,800,000 - 400,000) / 100,000 = 24. I'll buy that for the first approach where each byte is made a native 64-bit int. 3 * 8 bytes = 24 bytes. But for the third case where it's a single int? That makes no sense.

Code here in case I missed something:

package net.windward;

import java.util.Arrays;

public class TestSize {

    public static void main(String[] args) {

        new TestSize().runIt();
    }

    public void runIt() {
        System.out.println("The average memory used by MyColor1  is " + new SizeofUtil() {

            protected int create() {
                MyColor1[] aa = new MyColor1[100000];
                for (int ind = 0; ind < 100000; ind++)
                    aa[ind] = new MyColor1();
                return 1;
            }
        }.averageBytes());

        System.out.println("The average memory used by MyColor2  is " + new SizeofUtil() {

            protected int create() {
                MyColor2[] aa = new MyColor2[100000];
                for (int ind = 0; ind < 100000; ind++)
                    aa[ind] = new MyColor2();
                return 2;
            }
        }.averageBytes());

        System.out.println("The average memory used by MyColor3  is " + new SizeofUtil() {

            protected int create() {
                MyColor3[] aa = new MyColor3[100000];
                for (int ind = 0; ind < 100000; ind++)
                    aa[ind] = new MyColor3();
                return 1;
            }
        }.averageBytes());

        System.out.println("The average memory used by Integer[] is " + new SizeofUtil() {

            protected int create() {
                Integer[] aa = new Integer [100000];
                for (int ind = 0; ind < 100000; ind++)
                    aa[ind] = new Integer(ind);
                return 1;
            }
        }.averageBytes());

    }

    public abstract class SizeofUtil {
        public double averageBytes() {
            int runs = runs();
            double[] sizes = new double[runs];
            int retries = runs / 2;
            final Runtime runtime = Runtime.getRuntime();
            for (int i = 0; i < runs; i++) {
                Thread.yield();
                long used1 = memoryUsed(runtime);
                int number = create();
                long used2 = memoryUsed(runtime);
                double avgSize = (double) (used2 - used1) / number;
//            System.out.println(avgSize);
                if (avgSize < 0) {
                    // GC was performed.
                    i--;
                    if (retries-- < 0)
                        throw new RuntimeException("The eden space is not large enough to hold all the objects.");
                } else if (avgSize == 0) {
                    throw new RuntimeException("Object is not large enough to register, try turning off the TLAB with -XX:-UseTLAB");
                } else {
                    sizes[i] = avgSize;
                }
            }
            Arrays.sort(sizes);
            return sizes[runs / 2];
        }

        protected long memoryUsed(Runtime runtime) {
            return runtime.totalMemory() - runtime.freeMemory();
        }

        protected int runs() {
            return 11;
        }

        protected abstract int create();
    }

    class MyColor1 {
        byte red;
        byte green;
        byte blue;

        MyColor1() {
            red = green = blue = (byte) 255;
        }
    }

    class MyColor2 {
        byte[] color = new byte[3];
        private static final int red = 0;
        private static final int green = 1;
        private static final int blue = 2;

        MyColor2() {
            color[0] = color[1] = color[2] = (byte) 255;
        }
    }

    class MyColor3 {
        int color;
        private static final int red_shift = 0;
        private static final int green_shift = 8;
        private static final int blue_shift = 16;

        MyColor3() {
            color = 0xffffff;
        }
    }
}
like image 984
David Thielen Avatar asked Dec 07 '13 16:12

David Thielen


People also ask

When should I use byte instead of int?

The byte data type can be useful for saving memory in large arrays, where the memory savings actually matters. They can also be used in place of int where their limits help to clarify your code; the fact that a variable's range is limited can serve as a form of documentation.

What can be stored in byte Java?

A byte is just an 8-bit integer value. Which means it can hold any value from -2^7 to 2^7-1, which includes all of the number in {87, 79, 87, 46, 46, 46}.

Which is smaller byte or short?

short datatype is the variable range is more than byte but less than int and it also requires more memory than byte but less memory in comparison to int. The compiler automatically promotes the short variables to type int, if they are used in an expression and the value exceeds their range.

What is byte byte in Java?

The Java byte keyword is a primitive data type. It is used to declare variables. It can also be used with methods to return byte value. It can hold an 8-bit signed two's complement integer.


Video Answer


2 Answers

Since four bytes fit in an int, you could use a single int for your colors (and still have extra room for a byte if you want to add, say, alpha, later). Sample little group of methods (untested, just so you get the idea):

public int toIntColor(byte r, byte g, byte b) {
    int c = (int) r;
    c = (c << 8) | g;
    c = (c << 8) | b;
    return c;
}

And to get the bytes back:

public byte red(int c) {
    return c >> 16 & 0xFF;
}

public byte green(int c) {
    return c >> 8 & 0xFF;
}

public byte blue(int c) {
    return c & 0xFF;
}
like image 125
tckmn Avatar answered Oct 12 '22 11:10

tckmn


Your first approach seems being better than the other two. It will take 16 bytes on 64-bit JVM and 12 bytes on 32-bit. Second is the most expensive, third is also 16 bytes.

You could also store you colors in three matrixes of byte[width][height] if you're storing images, this would save you a lot of bytes. The idea is to give up MyColor class which takes additional 13 bytes per instance and thus save ~80% memory.

like image 20
Andrey Chaschev Avatar answered Oct 12 '22 11:10

Andrey Chaschev