Integer array or struct array - which is better?

2019-06-21 16:28发布

问题:

In my app, I'm storing Bitmap data in a two-dimensional integer array (int[,]). To access the R, G and B values I use something like this:

// read:
int i = _data[x, y];
byte B = (byte)(i >> 0);
byte G = (byte)(i >> 8);
byte R = (byte)(i >> 16);
// write:
_data[x, y] = BitConverter.ToInt32(new byte[] { B, G, R, 0 }, 0);

I'm using integer arrays instead of an actual System.Drawing.Bitmap because my app runs on Windows Mobile devices where the memory available for creating bitmaps is severely limited.

I'm wondering, though, if it would make more sense to declare a structure like this:

public struct RGB
{
    public byte R;
    public byte G;
    public byte B;
}

... and then use an array of RGB instead of an array of int. This way I could easily read and write the separate R, G and B values without having to do bit-shifting and BitConverter-ing. I vaguely remember something from days of yore about byte variables being block-aligned on 32-bit systems, so that a byte actually takes up 4 bytes of memory instead of just 1 (but maybe this was just a Visual Basic thing).

Would using an array of structs (like the RGB example` above) be faster than using an array of ints, and would it use 3/4 the memory or 3 times the memory of ints?

回答1:

If you are on about speed, then technically I would expect the int[] version to be faster, as there is a specific IL instruction for getting an int from an array (see OpCodes.Ldelem_I4). To do a custom struct it has to get the address (OpCodes.Ldelema) and then copy the struct (OpCodes.Ldobj) - processing the type metadata for both of those steps.

In short - the int approach should have better optimisations. But this is micro-optimisation - in general prefer the version that makes your code more readable. What you might consider is writing the struct with a custom static implicit conversion operator from int to your struct - then you can have the int[] and still do:

MyColor col = intArr[12];

(it'll do a static call in the middle, of course)

You might also consider using a union so you don't need to do lots of shifting:

IMPORTANT I haven't sanity checked the endianness on this; just change the offsets of R/G/B to change it.

class Program
{
    static void Main()
    {
        int[] i = { -1 };
        RGB rgb = i[0];
    }
}
[StructLayout( LayoutKind.Explicit)]
public struct RGB
{
    public RGB(int value) {
        this.R = this.G = this.B = 0; this.Value = value;
    }
    [FieldOffset(0)]
    public int Value;
    [FieldOffset(2)]
    public byte R;
    [FieldOffset(1)]
    public byte G;
    [FieldOffset(0)]
    public byte B;

    public static implicit operator RGB(int value) {
        return new RGB(value);
    }
    public static implicit operator int(RGB value) {
        return value.Value;
    }
}


回答2:

Why not just use the Color structure in System.Drawing? It's easier to handle and reference. Granted, it has 4 bytes (another value represents Alpha), but then so does your first implementation, and if I recall correctly, any 3 bytes will be aligned to a 4-byte block anyway. Take a look at the sample here.



回答3:

A lone byte on its own would probably get block-aligned, but multiple bytes in a struct can be packed. And according to Marshal.SizeOf, your RGB struct does indeed occupy only three bytes of memory. (Technically, this is the size when marshalling to unmanaged memory, and the CLR could choose to lay it out differently; but in practice I think this will be correct.)

However, the CLR could still insert padding between RGBs and other structs, and this behaviour may depend on the processor, CLR version etc. On a x86 system, allocating an array of 300 million RGB resulted in Task Manager reporting approximately 900 MB committed, whereas allocating an array of 300 million ints resulted in 1200 MB committed. So it looks like the 2.0 CLR on x86 is giving you the 25% saving. (I have to admit this surprised me; like Traveling Tech Guy, I expected the 3-byte structs to be aligned to a 4-byte boundary. So I may be missing something.) But the CF may be different: you'll really only know by testing it on your target platform.



回答4:

I think id depends on what do You want to achieve. It is certainly more Readabl to use to struct, and You can safely use it with

unsafe { }

which will really speed up Bitmap access. (IF you know what to do - no check on boundary conditions and stuff like that) And definately if You want to make operators for bitmap multiplication, masking, greyscaling, filtering the usual graphics stuff, then INT is your friend in terms of speed, but unfortunalely not in case of readability. Matrix filter only multiplies said ints (it can be written that way) not the RGB values independently, but with Marks structure that should not be the problem anyway. Hope it helps