Should we care about minimizing memory padding in Go?

1,446 views
Skip to first unread message

Peter Nguyen

unread,
Jan 19, 2014, 7:19:09 AM1/19/14
to golan...@googlegroups.com
Just read this article (http://www.geeksforgeeks.org/structure-member-alignment-padding-and-data-packing/) and as a suggestion to minimize padding, one could declare struct members in increasing/decreasing order of size. My question is does it apply to Go and is it a good practice to follow that rule?

Konstantin Khomoutov

unread,
Jan 19, 2014, 9:34:37 AM1/19/14
to Peter Nguyen, golan...@googlegroups.com

Kevin Gillette

unread,
Jan 19, 2014, 2:24:46 PM1/19/14
to golan...@googlegroups.com
If you don't know what the padding rules are (and padding is driven by a need for alignment), then a safe approach is to declare struct members in decreasing (biggest first) order of size -- this works fine for structs without any exported members. However, it's better to just properly learn what the rules are, and apply those sensibly, since for exported struct members are often poorly presented if they're simply arranged in size order. A good example of what to do:

type Node struct {
X, Y, Z int8
B       bool
Flag    uint32
Next    *Node
}

This struct will be precisely 12 bytes on typical 32-bit architectures, and precisely 16 bytes on typical 64-bit architectures, but requires absolutely no padding, because the fields are well packed within the alignment rules.

iirc, the required alignment for any type T is: min(sizeof(T), sizeof(uintptr)). I'm not sure how 8-byte numerics are handled on 32-bit platforms, though -- if those must be aligned, then the rule is just min(sizeof(T), 8).

So on 32-bit architectures (and the upcoming amd64 nacl target), no type will require being aligned to greater than a multiple of 4 bytes, and 1-byte types never need alignment. Since in the above Node type, there are four 1-byte fields, Flag, which needs to be aligned to a multiple of 4, will need no padding for alignment, and since the first five fields add up to 8 bytes total, that means that the pointer that follows will have 8 bytes of alignment already -- precisely what is needed for 64-bit pointers, and more than enough for 4-byte pointers. Further, since the whole struct is a multiple of 4 bytes of 32-bit architectures, and a multiple of 8 bytes on 64-bit platforms, a slice/array of Node will not require any padding between elements.

That said, don't worry about any padding that gets added at the end of a struct -- it's fairly inconsequential, and it's certainly not worth getting rid of fields (or adding junk fields -- which would be the same as padding) just to avoid that padding; large intra-field padding is where things get inefficient. For example, `[]struct { X byte; Y int64; Z byte }` requires 24 bytes per element on 64-bit platforms (7 bytes of padding following X to align Y to a multiple of 8, and another 7 bytes of trailing padding after Z to align elements to a multiple of 8). Compare this to XZY or YXZ orderings, which would require only 16 bytes per element.

speter

unread,
Jan 19, 2014, 3:14:56 PM1/19/14
to Kevin Gillette, golang-nuts
For example, `[]struct { X byte; Y int64; Z byte }` requires 24 bytes per element on 64-bit platforms

Does such a declaration really "require" 24 bytes per element, or is it just the layout used by the current compilers? I couldn't find anything in the spec that would prohibit a compiler from using the same memory layout for XYZ as for XZY or YZX.

Peter


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Kevin Gillette

unread,
Jan 19, 2014, 3:51:05 PM1/19/14
to golan...@googlegroups.com, Kevin Gillette
On Sunday, January 19, 2014 1:14:56 PM UTC-7, speter wrote:
Does such a declaration really "require" 24 bytes per element, or is it just the layout used by the current compilers? I couldn't find anything in the spec that would prohibit a compiler from using the same memory layout for XYZ as for XZY or YZX.

The current compilers don't rearrange the memory layout within a struct, afaik, since interoperability with C, assembly, syscall interfaces, etc would require workarounds to tell the compiler to not rearrange certain structs anyway -- the Go philosophy thus far has been to avoid that tradeoff, e.g. be compatible by default when possible. The other thing is that Go has always encouraged, if not expected, developers know, or be willing to learn, how processor/memory architectures work, so that they can design things with reasonable memory layouts themselves.

speter

unread,
Jan 22, 2014, 9:50:02 AM1/22/14
to Kevin Gillette, golang-nuts
Sorry for the delay.


That sounds reasonable. On the other hand, if it is a conscious design choice that optimizing the memory ordering within a struct is the developer's responsibility, I'd expect the spec to provide explicit guarantee that changing the order of fields in the declaration actually changes the order in memory. Without such a guarantee, trying to optimize by rearranging declarations feels like working against the language, or at least relying on hidden / unspecified behavior.

Is the lack of such a requirement / guarantee just an omission, or is it intentional?

Actually I originally took it to suggest that compliers would/could (eventually) implement optimizing the ordering of fields. It also occurred to me that it may be considered similarly to GC; even though manual control can potentially lead to better performance, it may not necessarily be worth the extra developer effort. In this case, in addition to manual experimenting with various orderings,  it would manifest in reduced maintainability due to having to declare fields in an order different from the "idiomatic" one (that is, the one considered optimal from readability / maintainability point of view).

I didn't thoroughly think about the feasibility, but it doesn't seem very far-fetched; it could be the case that a few heuristic rules can provide noticeable improvements, and from there manual control may only give minimal additional gains. In that case I'd prefer the compiler do the optimization, and keep the declarations in "idiomatic" order. Also perhaps a smart compiler/linker could detect types that are passed to non-Go code and inhibit reordering for those types, without adding explicit annotations. (Thinking about it now, I'd even consider making it a compiler/linker/vet/something error to pass structs requiring padding by the compiler to non-Go code.)

All in all, I'm fine with either manual or automatic memory ordering of struct fields, but I think the current ambiguity of the spec should be clarified, so that developers don't have to go through such thought experiment to decide whether they need to consider memory order, or can organize for optimal maintainability and still expect reasonable results in the long term.

Peter

Andy Balholm

unread,
Jan 22, 2014, 11:25:57 AM1/22/14
to golan...@googlegroups.com, Kevin Gillette
I don't think that the spec should specify the layout of structs in memory; that should be left up to the implementation. It only matters when you're using implementation-defined extensions, or unsafe.

Consider the gopherjs project, which compiles Go to JavaScript. At this point, it is not fully compliant with the spec, but I think it is good to keep the spec implementation-agnostic enough that it would be possible for a compile-to-JS implementation to comply with it. Mandating memory layout would prevent that.
Reply all
Reply to author
Forward
0 new messages