Struct Memory Alignment

Some programming languages provide structure syntax to group a set of data in a structural pattern. Usually the name is known as struct (in C and Go). There is such a coding problem called struct memory misalignment. This means we are wasting memory spaces in our structure definition.

The Problem

As we define the structure, we need to consider the memory allocation for each elements in the structure. Example:

amd64 cpu has the following memory allocation sizes for Go:

var basicSizes = [...]byte {
        Bool:       1,
        Int8:       1,
        Int16:      2,
        Int32:      4,
        Int64:      8,
        Uint8:      1,
        Uint16:     2,
        Uint32:     4,
        Uint64:     8,
        Float32:    4,
        Float64:    8,
        Complex64:  8,
        Complex128: 16,
}

When you start structuring the data sizes, say for example:

struct {
        a string
        b bool
        c string
        d bool
        e int8
        f int32
}

The compiler's memory allocation will looks as such:

struct {
        a string
        b bool
        _ [7]byte  // padding added by compiler
        c string
        d bool
        e int8
        f int32
        _ [2]byte  // padding added by compiler
}

As the compiler allocates memory, say the maximum size is 8, gives:

struct {
        a [8]byte
        a [8]byte
        b [1]byte
        _ [7]byte
        c [8]byte
        c [8]byte
        d [1]byte
        e [1]byte
        f [4]byte
        _ [2]byte
} // 56 bytes in total

Notice that we are wasting large number of padding memory to match the structure around b? This is called structure memory misalignment, where the memory spaces are wasted due to padding.

The solution is to structure the element accordingly to the memory alignment.

The Solution

Do the memory count as you design the structure. Example, by moving b element down the low bytes elements together, we save 8 bytes memory spaces.

// Sample code
struct {
        a string
        c string
        b bool
        d bool
        e int8
        f int32
}

Compiler will provides the following memory allocations with 1 byte padding space:

struct {
        a [8]byte
        a [8]byte
        c [8]byte
        c [8]byte
        b [1]byte
        d [1]byte
        e [1]byte
        f [4]byte
        _ [1]byte
} // 56 bytes in total

The Caveat

#1 - Hard to Optimize for Existing Large Bad Codes

The thing is that if your user already using some non-referencing structure initialization, it is highly difficult to optimize such structure. Example of bad code in Go:

        scenarios := []struct {
                inStatus   int
                inMessage  string
                inArg      interface{}
                inStdout   *bytes.Buffer
                inStderr   *bytes.Buffer
                outMessage string
        }{
                {
                        InfoStatus,
                        sample1,
                        nil,
                        &bytes.Buffer{},
                        &bytes.Buffer{},
                        "[ INFO ] " + sample1,
                }, {
                        WarningStatus,
                        sample1,
                        nil,
                        &bytes.Buffer{},
                        &bytes.Buffer{},
                        "[ WARNING ] " + sample1,
                }, {
                        ... continue 500 lines more

The above is the learning from Go's recommended table driven testing, where the values are assigned using the array style. If you want to restructure your struct, you need to realign all the values (that >500 lines of codes of editing and scrolling).

Hence, it is always good and right to fill in a structure using the reference. This way, you leaves room for developers to further optimize the structure without needing to alter your existing data set.

Good Example in Go:

        scenarios := []struct {
                inPrefix  int
                outPrefix int
        }{
                {
                        inPrefix:  NoTerminal,
                        outPrefix: NoTerminal,
                }, {
                        inPrefix:  BASHTerminal,
                        outPrefix: BASHTerminal,
                }, {
                        inPrefix:  SHTerminal,
                        outPrefix: SHTerminal,
                }, {
                        inPrefix:  DOSTerminal,
                        outPrefix: DOSTerminal,
                }, {
                        inPrefix:  -1,
                        outPrefix: NoTerminal,
                }, {
                        inPrefix:  -5000,
                        outPrefix: NoTerminal,
                }, {
                        ... continue 500 lines more
        }

Example in C:

employee emp = {
        .id = 0, 
        .name = "none"
};

OR 

struct iio_chan_spec stm32_lptim_cnt_channels = {
        .type = IIO_COUNT,
        .channel = 0,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                BIT(IIO_CHAN_INFO_ENABLE) |
                                BIT(IIO_CHAN_INFO_SCALE),
        .ext_info = stm32_lptim_cnt_ext_info,
        .indexed = 1,
};


#2 - Compiler Specifics

Such memory optimization are sometimes specific to compiler depending on languages like C. However, developers are strongly not to rely on compiler to write his/her codes when a disciplined practices is available.

In Summary

To avoid over-padding your structure memory, you MUST:

  1. Organize the structure based on memory alignment.
  2. ALWAYS assign values using reference style.
  3. ALWAYS forbids array style assignment.