I have a struct of 4 fields of types that come from template parameters:
template <typename T1, typename T2, typename T3, typename T4>
struct __attribute__((aligned(8))) four_tuple {
typedef struct {
T1 t1;
T2 t2;
T3 t3;
T4 t4;
} payload;
payload p;
};
Each type T1
, T2
, T3
, and T4
, is guaranteed to be a primitive type or a four_tuple<...>::payload
type. The guarantees are recursive - you can think of the struct as encoding a quadtree whose leaf nodes are primitive types.
My goal is for the struct to have minimum possible sizeof
, subject to the condition that all of the leaf nodes are properly aligned. The tools allowed for the optimization are class template specializations using:
- reordering of fields
t1
,t2
,t3
,t4
- addition of filler fields
- gcc attribute
packed
onpayload
- maybe others?
I feel like there is a clever solution to this problem using enable_if
and SFINAE. Can anyone find it?
To illustrate the problem, if we use the above implementation as-is using Foo = four_tuple<char,double,char,double>
, we'll have a size of 32 for the payload and overall. If we simply declare the payload packed
, the double
's will not be well-aligned. A template specialization that reorders the fields in decreasing order (here, double, double, char, char
) will give a payload and overall size of 24. But the extra 6 bytes it uses are wasteful, as can be seen by considering using Bar = four_tuple<Foo::payload,int,int,int>
. With optimal packing Bar
could fit in 32 bytes, but with this scheme it would require 40. Bluntly applying field-reordering with packed
will result in misaligned int
's in Bar
- some filler is needed.
I know that in general restructuring the memory layout of a struct's fields can have performance implications due to cache considerations, and that in general those implications will be at least as significant as any potential gains from better packing. I'd like to explore the tradeoffs, though, and I can't really do that properly in my context without solving this problem.
packed
, what's the problem? Using it in another type would not give any misaligned fields, since the compiler would fix that with padding already. – Gurleychar
s, you want a size of 4, don't you? That can't require an alignment of 8. – Gurleyfour_tuple<Foo::payload,int,int,int>
can achieve a size of 32 with no misalignment if properly packed. If you don't addpacked
, it will have a size of 40. – Pinnatifidfour_tuple
. If a particular instance of thefour_tuple
is misaligned, all bets are off. That's why I align 8. – Pinnatifidfour_tuple<Foo::payload,int,int,int>
can't achieve a size of 32.Foo::payload
can't have a size smaller than 24 (using your platform's sizes) if you want to guarantee alignment, and you need three moreint
s. There's no way to re-use the padding in a member. – GurleyFoo
packed, then an arrayFoo[2]
will have at least one misaligned element, and you say you want to avoid misaligned elements, right? – GurleyFoo
(double, double, char, char). Its (packed) payload can fit in 18 without misalignment. I say "without misalignment" assuming it is used only within the context of a template parameter offour_tuple
- thepayload
struct is not meant to be publicly visible (I should make this clearer in my post). Also,Foo
is not declared packed precisely so things likeFoo[2]
will work. – Pinnatifid