You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:23:21