C++编程实践——gcc中的内存大页应用

一、大页的处理

在前面的相关文章分析中,对内存大页的相关内容和应用进行了多次分析,基本对大页内存的技术已基本掌握。但如何在实际开发如何使用大页内存,还需要结合具体的开发平台进行分析,从而能真正的将技术应用到实践。
下面就对在Linux平台上如何使用gcc进行大页处理进行整体的分析说明。重点针对热函数如何直接映射入大页。

二、C/C++的热函数

Hot Functions,热函数。其实从名字容易理解,这个函数会被高频的调用或者说执行的时间占用所占比例很高。这和前面的perf等性能监测工具中的“热”是一个道理。其实从前面的分析也可以看出来,热调用相关的代码在哪个方面都非常重要。所以其优化往往能带来更好的性能,一般来说,主要的优化方法有以下几种:

  1. 编译期的优化,即使用编译相关优化的选项和参数
  2. 使用内联函数,这个在前面分析过
  3. 内存优化,提高命中率
  4. 降低循环中的不当处理,不过一般来说,现代编译器对这个处理基本都可以比较好的应对。
    在上面的内存优化中,除了基本的内存对齐、预取等相关的方法外,一个重要的方法就是将热函数映射到内存大页内,从而在根本上提高内存的命中率。本文也重点对此进行分析说明。

三、GCC中映射热函数到大页的方法

在Linux平台上,GCC可以下面的代码将热函数映射到内存大页:

  1. 直接编码实现内存大页映射
  2. 使用链接器脚本
  3. 使用gcc9版本以上的优化配置

另外,其实在Linux平台上,还可以直接配置大页进行开发(即直接启用透明大页),正如前面的DPDK中一样(具体的细节可参看前面的DPDK中相关的细节)。

四、例程

下面看上面几种方法的相关例程:

  1. 直接代码处理
#include <sys/mman.h>
#include <string.h>
#include <stdio.h>

void hotFunc1() {
  /*......*/
}

void hotFunc2() {
    /*......*/
}

// 映射大页内存
int hotFuncToHugePage() {
    // 获取函数地址和大小
    void* func1 = (void*)hotFunc1;
    void* func2 = (void*)hotFunc2;
    
    // 计算热函数代码大小(需要根据实际情况调整)
    size_t codeSize = 2 * 1024 * 1024; // 2MB大页
    
    // 申请大页内存
    void* addr = mmap(NULL, codeSize, 
                              PROT_READ | PROT_WRITE | PROT_EXEC,
                              MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, 
                              -1, 0);
    
    if (addr == MAP_FAILED) {
        return -1;
    }
    
    //拷贝并计算热函数代码
    memcpy(addr, func1, codeSize);
    
    // 重点:刷新指令缓存
    __builtin___clear_cache(addr, 
                           (char*)addr + codeSize);
    
    return 0;
}

编译使用普通的编译方法即可。

  1. 使用脚本

创建脚本hp_layout.ld:


SECTIONS {
    .text : {
        *(.text)
    }
    
    /* 热函数单独处理到指定段,2MB对齐 */
    .text.hot : {
        . = ALIGN(2M);  
        __hot_text_start = .;
        *(.text.hot)
        *(.text.hot.*)
        . = ALIGN(2M);
        __hot_text_end = .;
    }
    
    .data : {
        *(.data)
    }
    
    .bss : {
        *(.bss)
    }
}
// 利用section属性标记热函数
#define HOT_FUNC __attribute__((section(".text.hot")))

// 热函数声明
HOT_FUNC void processData(int* , size_t );
HOT_FUNC void matrixMult(float** , float** , float** , int );

// hot func
HOT_FUNC void processData(int* d, size_t len) {
    //相关实现代码
}

HOT_FUNC void matrixMult(float** a, float** b, float** ret, int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            for (int k = 0; k < n; k++) {
                //具体代码
            }
        }
    }
}

编译方法如下:

# 调用上面创建的脚本
gcc -Wl,-T,hp_layout.ld -Wl,-zcommon-page-size=0x200000 -Wl,-zmax-page-size=0x200000 -O3 hot_func.c -o hotTest

# 也可以直接操作
gcc -Wl,--section-start=.text.hot=0x200000 -O3 hot_func.c -o hotTest
  1. gcc9以后的编译处理方式
// GCC的函数节特性
__attribute__((section(".text.hot"))) 
__attribute__((optimize("O3"))) 
void hotFunc() {
    //相关代码
}

// 热函数分组
__attribute__((section(".text.hot.group1"))) 
void hotFuncGroup1() { /* ... */ }

__attribute__((section(".text.hot.group2"))) 
void hotFuncGroup2() { /* ... */ }

编译方法如下:

gcc -ffunction-sections -fdata-sections -O3 -Wl,--gc-sections -Wl,-Map=hot_map.txt test.c -o test

代码仅作为示例,重点在GCC的处理上,而不在代码实现上。

五、总结

其实很多开发者的技术水平已经不错了,但在实际开发中为什么很多的技术应用起来却不太顺畅?主要原因就在于一些技术与平台环境无法有机的整合在一起。就像人们说的,哪个零件都是好的,但组合在一起就不好使了。
什么意思呢?就是技术的应用一定要和实际的环境结合起来,不但有技术平台还有环境平台等。所以,给大家一个建议,不但要学好技术,还要把技术周边的相关应用掌握清楚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值