前言
结构数据布局优化(Structure Data Layout Optimizations)是比较成熟也是使用广泛的编译器优化技术,旨在提高数据局部性,减少cache miss。常用的structure optimizations有:结构体拆分(structure spliting)、结构体剥离(structure peeling)、域重组(structure reordering)。
1.结构体拆分
结构体拆分是将结构体中冷的域(不常使用的字段)和热的域(经常访问的字段)拆开,分成多个子结构体,并在原结构体中增加指针域和其它子结构体相连,拆分之后的组件也将分配给寄存器,加快访问速度。示例如下:
typedef struct Node
{
int a;
char ch;
double d;
long l;
int *v;
}node;
// 内存分配
int size = 10;
node *my_node = (node *)calloc(size, sizeof(node));
原结构体拆分后:
typedef struct New_node
{
node_1 *node_hot;
node_2 *node_cold;
}new_node;
typedef struct Node_1
{
int a;
char ch;
double d;
}node_1;
typedef struct Node_2
{
long l;
int *v;
}node_2;
// 内存分配
node_1 *arr_1= (node_1 *)calloc(size, sizeof(node_1));
node_2 *arr_2= (node_2 *)calloc(size, sizeof(node_2));
//关联他们
for (i=0; i < N; i++)
{
my_node[i]. node_hot = &arr_1[i];
my_node[i]. node_cold = &arr_2[i];
}
进行spliting之后的内存布局:
通常这些子结构体还可以递归拆分,采用同样的指针方式与父结构体相连。这种拆分一方面能将较大的结构体中热的域拆分成新的结构体,提高了缓存局部性,另一方面是这种转换引入了指针间接寻址,增加了结构体开销,这种优化并不一定有利,因此,需要一种良好的决策算法来有效控制此转换。
2.结构体剥离
结构体剥离是一种将原结构体中的域剥离成多个新的结构体的优化,我们将原始结构剥离到单独的结构中,以便结构重组和字段的内存连续紧密分配,同时将对象指针改为整数索引,通过数组索引访问的方式替换指针之间的多层间接寻址。示例如下:
typedef struct Acs
{
int a;
int b;
acs *next;
acs *acs_p;
}acs;
// 内存分配
acs *arr= (acs *)calloc(size, sizeof(acs));
arcs *tmp_node = arr;
while(tmp_node)
{
tmp_node->a = 111;
tmp_node=tmp_node->next;
}
原结构体剥离后:
typedef struct Acs_sub1
{
unsigned int next_index;
}acs_sub1;
typedef struct Acs_sub2
{
int a;
int b;
unsigned int acs_pi;
}acs_sub2;
// 内存分配
int measu = sizeof(acs_sub1) + sizeof(acs_sub2);
acs *arr = (acs *)calloc(size, measu);
acs_sub1 *ac_1 = (acs_sub1 *)arr;
acs_sub2 *ac_2 = (acs_sub2 *)(ac_1 + size);
int tmp=0;
while(tmp!=-1)
{
ac_2[tmp].a = 111;
tmp = ac_1[tmp].next_index;
}
或者以下这种情况:
struct MyStruct
{
double value1;
double value2;
int i;
};
MyStruct *t;
//
struct MyStruct2
{
double value2;
int i;
};
double *d1;
MyStruct2 *s2;
换句话说就是:结构剥离转换是结构拆分的一种特殊情况,其中不需要引入指针。当一个变量失效时(以后将不使用其值)时,已被分配给它的寄存器将被重用。
3.域重组
域重组是通过修改结构体中域的顺序来改变内存的对齐方式,以提高数据的亲和性。在大型的结构体中,根据某些字段的访问频率对字段顺序进行调整来提高效率,这种转换不像结构体拆分和剥离那样激进,但这种转换也使用在结构体拆分和剥离中。例如有如下结构体和使用代码:
typedef struct T
{
int a;
int b;
int c;
}t;
val = &t.a; ++val; 使用*p
//假如结构体t,有这样的使用代码,当对结构体t进行reorder之后会怎样?
typedef struct T
{
int c;
int b;
int a;
}t;
此时代码将会出问题,指针将会指向其它地址。如果使用t->a这样方式是没问题的,但这问题怎么解决,这就是一个“第”几个概念,采用索引的方式来实现reorder,这样不用考虑访问时的offset不固定问题,进行reorder的时候只需要对这个’id’进行操作就可以很好的解决这个问题。
typedef struct T
{
0 -> int a;
1 -> int b;
2 -> int c;
}t;
4.References:
- Structure Layout Optimizations in the Open64 Compiler: Design, Implementation and Measurements
- Struct-reorg: current status and future perspectives
- Array Regrouping and Structure Splitting Using Whole-Program Reference Affinity