在不丢失对齐的情况下优化打包递归模板化结构
Optimally packing a recursively templated struct without loss of alignment
我有一个来自模板参数的 4 个类型字段的结构:
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;
};
每种类型 T1
、T2
、T3
和 T4
都保证是原始类型或 four_tuple<...>::payload
类型。保证是递归的 - 您可以将结构视为对 quadtree 进行编码,其叶节点是原始类型。
我的目标是使结构具有尽可能小的 sizeof
,前提是所有叶节点都正确对齐。允许优化的工具是 class 模板专业化使用:
- 重新排序字段
t1
、t2
、t3
、t4
- 添加填充字段
payload
上的 packed
gcc 属性
- 也许还有其他人?
我觉得使用 enable_if
和 SFINAE 可以巧妙地解决这个问题。有人能找到吗?
为了说明这个问题,如果我们按原样使用上述实现 using Foo = four_tuple<char,double,char,double>
,我们的有效负载和整体大小将是 32。如果我们简单地声明有效载荷 packed
,double
将不会很好地对齐。以降序重新排序字段的模板特化(此处为 double, double, char, char
)将提供 24 的有效负载和总大小。但它使用的额外 6 个字节是浪费的,考虑 using Bar = four_tuple<Foo::payload,int,int,int>
可以看出.使用最佳打包 Bar
可以容纳 32 个字节,但使用此方案需要 40 个字节。直接使用 packed
应用字段重新排序将导致 [=29= 中的 int
未对齐] - 需要一些填充物。
我知道在一般情况下,由于缓存方面的考虑,重构结构字段的内存布局可能会对性能产生影响,并且通常这些影响至少与更好的包装带来的任何潜在收益一样重要。不过,我想探索权衡,如果不解决这个问题,我就无法在我的上下文中正确地做到这一点。
您的嵌套元组案例中的大问题是您想要一个类型为 four_tuple<char,double,char,double>::payload
的字段,就像它是 four_tuple<char,double,char,double>
一样对齐,但不需要容器类型继承它的结盟。这是复杂的。这样做是可能的,但它会使您的代码无法移植到 GCC 以外的任何地方。我想这没关系,因为您已经在您的问题中建议了 GCC 扩展。基本思想是可以用bit-fields插入padding保证对齐:
struct __attribute__((packed)) S {
char c; // at offset 0
int i; // at offset 1, not aligned
int : 0;
int j; // at offset 8, aligned
int : 0;
int k; // at offset 12, no extra padding between j and k
};
int
当然是一个非常具体的类型,有非常具体的对齐方式,你需要一个动态确定的对齐方式。幸运的是,GCC 允许类型 char
的 bit-fields,通常只强制字节对齐,与 alignas
组合,确保任意对齐。
完成后,您可以检查所有 24 种可能的字段排序和 select 提供最小总大小的有效负载。我将有效负载设为全局类型,并为其提供了一个额外的模板参数来指示字段顺序。这允许 tuple4<T1, T2, T3, T4>
按顺序检查 tuple4_payload<T1, T2, T3, T4, 1234>
、tuple4_payload<T1, T2, T3, T4, 1243>
等并选择最好的一个。
template <typename...> struct smallest;
template <typename...T> using smallest_t = typename smallest<T...>::type;
template <typename T> struct smallest<T> { using type = T; };
template <typename T, typename...Ts> struct smallest<T, Ts...> { using type = std::conditional_t<sizeof(T) <= sizeof(smallest_t<Ts...>), T, smallest_t<Ts...>>; };
template <typename T1, typename T2, typename T3, typename T4> struct tuple4;
template <typename T1, typename T2, typename T3, typename T4, int fieldOrder> struct tuple4_payload;
template <typename T1, typename T2, typename T3, typename T4> struct tuple4_simple { T1 t1; T2 t2; T3 t3; T4 t4; };
template <typename T> struct extract_payload { using type = T; };
template <typename...T> struct extract_payload<tuple4<T...>> { using type = typename tuple4<T...>::payload; };
template <typename T> using extract_payload_t = typename extract_payload<T>::type;
#define PERMS \
PERM(1,2,3,4) PERM(1,2,4,3) PERM(1,3,2,4) PERM(1,3,4,2) PERM(1,4,2,3) PERM(1,4,3,2) \
PERM(2,1,3,4) PERM(2,1,4,3) PERM(2,3,1,4) PERM(2,3,4,1) PERM(2,4,1,3) PERM(2,4,3,1) \
PERM(3,1,2,4) PERM(3,1,4,2) PERM(3,2,1,4) PERM(3,2,4,1) PERM(3,4,1,2) PERM(3,4,2,1) \
PERM(4,1,2,3) PERM(4,1,3,2) PERM(4,2,1,3) PERM(4,2,3,1) PERM(4,3,1,2) PERM(4,3,2,1)
#define PERM(a,b,c,d) \
template <typename T1, typename T2, typename T3, typename T4> \
struct __attribute__((packed)) tuple4_payload<T1, T2, T3, T4, a##b##c##d> { \
char : 0 alignas(T##a); extract_payload_t<T##a> t##a; \
char : 0 alignas(T##b); extract_payload_t<T##b> t##b; \
char : 0 alignas(T##c); extract_payload_t<T##c> t##c; \
char : 0 alignas(T##d); extract_payload_t<T##d> t##d; \
};
PERMS
#undef PERM
#define PERM(a,b,c,d) , tuple4_payload<T1, T2, T3, T4, a##b##c##d>
template <typename, typename...T> using tuple4_smallest_payload_t = smallest_t<T...>;
template <typename T1, typename T2, typename T3, typename T4>
struct alignas(tuple4_simple<T1, T2, T3, T4>) tuple4 : tuple4_smallest_payload_t<void PERMS> {
using payload = tuple4_smallest_payload_t<void PERMS>;
};
#undef PERM
在您的情况下,您可以将其用作 tuple4<int, tuple4<char, double, char, double>, int, int>
。请注意,即使此处未明确提及负载类型,它仍将用于 t2
成员。
我有一个来自模板参数的 4 个类型字段的结构:
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;
};
每种类型 T1
、T2
、T3
和 T4
都保证是原始类型或 four_tuple<...>::payload
类型。保证是递归的 - 您可以将结构视为对 quadtree 进行编码,其叶节点是原始类型。
我的目标是使结构具有尽可能小的 sizeof
,前提是所有叶节点都正确对齐。允许优化的工具是 class 模板专业化使用:
- 重新排序字段
t1
、t2
、t3
、t4
- 添加填充字段
payload
上的 - 也许还有其他人?
packed
gcc 属性
我觉得使用 enable_if
和 SFINAE 可以巧妙地解决这个问题。有人能找到吗?
为了说明这个问题,如果我们按原样使用上述实现 using Foo = four_tuple<char,double,char,double>
,我们的有效负载和整体大小将是 32。如果我们简单地声明有效载荷 packed
,double
将不会很好地对齐。以降序重新排序字段的模板特化(此处为 double, double, char, char
)将提供 24 的有效负载和总大小。但它使用的额外 6 个字节是浪费的,考虑 using Bar = four_tuple<Foo::payload,int,int,int>
可以看出.使用最佳打包 Bar
可以容纳 32 个字节,但使用此方案需要 40 个字节。直接使用 packed
应用字段重新排序将导致 [=29= 中的 int
未对齐] - 需要一些填充物。
我知道在一般情况下,由于缓存方面的考虑,重构结构字段的内存布局可能会对性能产生影响,并且通常这些影响至少与更好的包装带来的任何潜在收益一样重要。不过,我想探索权衡,如果不解决这个问题,我就无法在我的上下文中正确地做到这一点。
您的嵌套元组案例中的大问题是您想要一个类型为 four_tuple<char,double,char,double>::payload
的字段,就像它是 four_tuple<char,double,char,double>
一样对齐,但不需要容器类型继承它的结盟。这是复杂的。这样做是可能的,但它会使您的代码无法移植到 GCC 以外的任何地方。我想这没关系,因为您已经在您的问题中建议了 GCC 扩展。基本思想是可以用bit-fields插入padding保证对齐:
struct __attribute__((packed)) S {
char c; // at offset 0
int i; // at offset 1, not aligned
int : 0;
int j; // at offset 8, aligned
int : 0;
int k; // at offset 12, no extra padding between j and k
};
int
当然是一个非常具体的类型,有非常具体的对齐方式,你需要一个动态确定的对齐方式。幸运的是,GCC 允许类型 char
的 bit-fields,通常只强制字节对齐,与 alignas
组合,确保任意对齐。
完成后,您可以检查所有 24 种可能的字段排序和 select 提供最小总大小的有效负载。我将有效负载设为全局类型,并为其提供了一个额外的模板参数来指示字段顺序。这允许 tuple4<T1, T2, T3, T4>
按顺序检查 tuple4_payload<T1, T2, T3, T4, 1234>
、tuple4_payload<T1, T2, T3, T4, 1243>
等并选择最好的一个。
template <typename...> struct smallest;
template <typename...T> using smallest_t = typename smallest<T...>::type;
template <typename T> struct smallest<T> { using type = T; };
template <typename T, typename...Ts> struct smallest<T, Ts...> { using type = std::conditional_t<sizeof(T) <= sizeof(smallest_t<Ts...>), T, smallest_t<Ts...>>; };
template <typename T1, typename T2, typename T3, typename T4> struct tuple4;
template <typename T1, typename T2, typename T3, typename T4, int fieldOrder> struct tuple4_payload;
template <typename T1, typename T2, typename T3, typename T4> struct tuple4_simple { T1 t1; T2 t2; T3 t3; T4 t4; };
template <typename T> struct extract_payload { using type = T; };
template <typename...T> struct extract_payload<tuple4<T...>> { using type = typename tuple4<T...>::payload; };
template <typename T> using extract_payload_t = typename extract_payload<T>::type;
#define PERMS \
PERM(1,2,3,4) PERM(1,2,4,3) PERM(1,3,2,4) PERM(1,3,4,2) PERM(1,4,2,3) PERM(1,4,3,2) \
PERM(2,1,3,4) PERM(2,1,4,3) PERM(2,3,1,4) PERM(2,3,4,1) PERM(2,4,1,3) PERM(2,4,3,1) \
PERM(3,1,2,4) PERM(3,1,4,2) PERM(3,2,1,4) PERM(3,2,4,1) PERM(3,4,1,2) PERM(3,4,2,1) \
PERM(4,1,2,3) PERM(4,1,3,2) PERM(4,2,1,3) PERM(4,2,3,1) PERM(4,3,1,2) PERM(4,3,2,1)
#define PERM(a,b,c,d) \
template <typename T1, typename T2, typename T3, typename T4> \
struct __attribute__((packed)) tuple4_payload<T1, T2, T3, T4, a##b##c##d> { \
char : 0 alignas(T##a); extract_payload_t<T##a> t##a; \
char : 0 alignas(T##b); extract_payload_t<T##b> t##b; \
char : 0 alignas(T##c); extract_payload_t<T##c> t##c; \
char : 0 alignas(T##d); extract_payload_t<T##d> t##d; \
};
PERMS
#undef PERM
#define PERM(a,b,c,d) , tuple4_payload<T1, T2, T3, T4, a##b##c##d>
template <typename, typename...T> using tuple4_smallest_payload_t = smallest_t<T...>;
template <typename T1, typename T2, typename T3, typename T4>
struct alignas(tuple4_simple<T1, T2, T3, T4>) tuple4 : tuple4_smallest_payload_t<void PERMS> {
using payload = tuple4_smallest_payload_t<void PERMS>;
};
#undef PERM
在您的情况下,您可以将其用作 tuple4<int, tuple4<char, double, char, double>, int, int>
。请注意,即使此处未明确提及负载类型,它仍将用于 t2
成员。