如何在GCC中添加Pass实现Structure Peeling并获取函数参数类型
GCC实现Structure Peeling(结构体拆分)Pass的技术指南
需求概述
我需要在GCC中实现一个Pass来完成**Structure Peeling(结构体拆分)优化:通过将结构体拆分为热字段(频繁访问)和冷字段(极少访问)**的分离存储,提升缓存命中率,进而优化程序运行效率。该优化在SPEC-CPU2006的462.libquantum等测试用例中能带来显著性能提升,类似优化已在ICC中支持。
优化示例
原始代码(存在缓存低效问题):
// origin struct node { float a; long b; }; // in most functions, 'b' is visited and 'a' is not void demo_func(struct node* nodes, int size) { // 'size' is a huge number here for (int i = 0; i < size; i++) { nodes[i]->b += 13; // just for example } }
优化后代码(拆分结构体,提升缓存效率):
struct node { float* a; long* b; }; void demo_func(struct node* nodes, int size) { // 'size' is a huge number here for (int i = 0; i < size; i++) { nodes->b[i] += 13; } }
当前困境
我已将Pass置于ipa-late-pass阶段,但因对GCC内部机制不熟悉推进困难。其中一个具体问题:如何在Pass中获取函数参数的具体结构体类型(如struct node),而非仅得到通用的RECORD_TYPE?相关代码片段如下:
unsigned int pass_struct_split::execute (function *) { struct cgraph_node *node; FOR_EACH_DEFINED_FUNCTION (node) { auto param = DECL_ARGUMENTS (node->decl); for (; param; param = DECL_CHAIN (param)) { // I want to get the specific type of the param, something like `struct node` // Is that possible? Or do I only get the `RECORD_TYPE` instead of the specific `struct node`? // I'm confused here. } } }
具体问题解决方案:获取参数的具体结构体类型
你可以通过GCC的tree类型接口逐层解析参数类型,最终获取结构体的具体名称:
unsigned int pass_struct_split::execute (function *) { struct cgraph_node *node; FOR_EACH_DEFINED_FUNCTION (node) { auto param = DECL_ARGUMENTS (node->decl); for (; param; param = DECL_CHAIN (param)) { tree param_type = TREE_TYPE(param); // 解析指针类型:如果参数是结构体指针,先解引用得到结构体类型 if (POINTER_TYPE_P(param_type)) { param_type = TREE_TYPE(param_type); } // 确认是结构体类型(RECORD_TYPE) if (TREE_CODE(param_type) == RECORD_TYPE) { tree type_decl = TYPE_NAME(param_type); if (type_decl && TREE_CODE(type_decl) == TYPE_DECL) { // 获取结构体的标识符节点 tree struct_id = DECL_NAME(type_decl); if (struct_id) { // 转换为字符串,得到具体的结构体名称(如"struct node") const char* struct_name = IDENTIFIER_POINTER(struct_id); // 后续可基于此名称做结构体的匹配与处理 } } } } } return 0; }
核心逻辑:先判断参数是否为指针类型,解引用后得到结构体的RECORD_TYPE,再通过TYPE_NAME和DECL_NAME获取结构体的具体标识符名称。
全面实现Structure Peeling Pass的技术建议
1. 确认Pass阶段的合理性
选择ipa-late-pass是合适的:
- 该阶段处于**过程间分析(IPA)**后期,能收集跨函数的结构体字段访问信息,更准确地判断热/冷字段
- 避免在早期Pass阶段做变换,此时IR尚未稳定,可能被后续优化破坏
2. 收集结构体字段的访问统计
要完成拆分,首先需要准确识别热/冷字段:
- 遍历所有函数的GIMPLE IR,通过
gimple_stmt_iterator遍历每一条语句 - 使用
get_ref_base_and_extent接口解析结构体字段的引用,识别出访问的具体字段 - 为每个结构体(
RECORD_TYPE)维护统计信息:- 每个字段的总访问次数
- 字段是否在循环中被批量访问(循环内的连续访问是缓存优化的关键场景)
- 字段的访问者范围(是否被多个函数频繁访问)
3. 判定需要拆分的结构体
设定筛选规则,选择收益最大的结构体进行拆分:
- 热字段的总访问占比超过阈值(比如80%),且冷字段几乎不被访问
- 结构体本身较大(比如超过64字节,超过L1缓存行大小),拆分后能显著减少缓存无效加载
- 排除存在直接内存操作的结构体(比如使用
memcpy直接复制整个结构体、指针强制转换访问内存的场景,这类场景拆分后易破坏语义)
4. 执行结构体拆分与代码变换
确定目标结构体后,完成IR层面的变换:
- 生成新结构体类型:创建包含热/冷字段指针的新RECORD_TYPE,替换原结构体类型
- 全局变量转换:如果存在全局的原结构体数组,拆分出两个独立的热/冷字段数组,将全局变量替换为新结构体类型,指针指向对应数组
- 函数参数与内部代码转换:
- 将函数参数中的原结构体指针替换为新结构体指针
- 遍历函数内的IR,将原字段访问(如
nodes[i]->b)转换为新的数组访问(如nodes->b[i])
- 调用点更新:遍历所有函数调用点,更新传递的参数,确保与新结构体类型匹配
5. 处理边缘场景
- 动态内存分配:将
malloc(sizeof(struct node) * size)替换为分配两个数组(热字段数组和冷字段数组),并构造新结构体数组指向这两个数组 - 兼容性处理:对无法自动转换的场景(如外部函数调用、汇编代码访问),可选择跳过该结构体的拆分,或添加适配层
6. 调试与验证
- 使用
-fdump-tree-all选项生成IR dump文件,检查Pass执行后的IR是否符合预期 - 在SPEC-CPU2006等测试用例上编译运行,对比优化前后的性能指标(运行时间、缓存命中率)
- 验证程序功能正确性,避免因结构体拆分导致的语义错误
内容的提问来源于stack exchange,提问作者Dbettkk
相关产品推荐
相关产品推荐

