各位同学大家好,欢迎大家参加毕昇编译器的体验活动,我是本期活动的分享嘉宾。接下来我将给大家分享一下如何使用毕昇编译器进行 PGO反馈优化 。
PGO 介绍
PGO 是 Profile Guided Optimization 的缩写,即档案导引优化。是一种自适应优化手段,不需要对软件代码本身做出改进,即可获得性能的提升,目前已经应用于GCC、VC++、Clang等常见的编译器中。
配置文件信息可以帮助编译器更好地优化。例如,知道分支被非常频繁地执行,有助于编译器在 排序基本块 时做出更好的决策;知道函数foo的调用频率比另一个函数条的调用频率更高,有助于 内联 。此外,配置文件还能指导编译器更好的进行 冷热分区 、 函数分区 、 寄存器分配 、 循环展开和剥离 、 增强代码和数据的局部相关性、Switch语句扩展 (建议使用-O2以上的优化等级使用PGO)。
毕昇支持两种类型的PGO采样。一种是使用采样分析器,可以在牺牲很小运行时开销情况下生成配置文件。二是重新构建代码获取一个插桩版本的二进制以收集更详细的配置文件信息。这两种配置文件都可以提供代码中指令的执行计数,以及关于所采取的分支和函数调用的信息。
使用时需要注意,插桩编译和反馈编译时的源代码要完全一致,有时编译选项也要一致,否则可能导致无法关联profile数据或做出糟糕的优化选择,最好是在代码通过了单元测试和回归测试后再使用。另外,若程序大部分时间花在函数调用和分支跳转时,PGO优化收益大,若大部分时间在循环和计算时,PGO收益较小。如图是PGO的优化过程: 
图 1-1 PGO优化过程
步骤 1 准备工作
毕昇编译器的体验流程需要在鲲鹏平台上完成,因此我们需要申请鲲鹏远程实验室的服务器资源。这个时候有的同学可能会头疼了,我没有操作过服务器呀,应该怎么去做呀?通过黑框操作服务器好不方便,太麻烦了。 为了降低大家在鲲鹏平台上体验的使用门槛,我们推出了云开发环境,首先申请简单,输入邮箱地址后就可以申请到鲲鹏远程实验室资源,其次操作简单,申请完成后可以直接通过在线IDE的方式打开页面进行操作。整个体验就突出两个字,顺滑! 首先我们访问鲲鹏社区的远程实验室页面: https://www.hikunpeng.com/zh/developer/cloud-lab ,申请云开发环境: 
之后我们输入自己的邮箱地址,选择 openEuler 20.03 LTS-SP3 系统,因为专项软件迁移功能体验过程相对简单,1天的体验时间完全足够,这样也方便后面体验的小伙伴快速申请到体验资源。申请成功后我们点击“打开在线IDE”按钮,进入云开发环境中,云开发环境给大家提供了在线 Vscode 页面,方便我们直接在鲲鹏上进行开发。 
步骤 2 创建code.c文件,并将如下代码拷贝至code.c文件中
步骤 3 获取未优化的情况
说明:默认使用-O0,是最安全,但也是性能最差的。
步骤 4 获取基准值
说明:相比于-O3的一些激进不稳定的优化,-O2是应用程序被推荐的优化等级,可以使用很多已知且稳定的优化。
步骤 5 测试 PGO 效果
说明:相比于步骤2的结果,这个更进一步得拿到了一些收益。
步骤 6 对比使用PGO优化前后热点情况

图1-2 对比使用PGO优化前后热点情况
说明:使用topdown工具分析,我们能够知道这个应用的瓶颈主要在 backend_bound, 这里抓了 cycles 事件,可以看到优化后 cycles数量有明显的下降。同时我们也能看到,优化前对内联函数的策略比较激进,热点函数在main, 优化后 sort_array函数没有内联,这种指导函数内联的优化,在一些 frontend_bound 很高的应用有很好的效果。
步骤 7 对比使用PGO优化前后冒泡排序函数bubble_sort的反汇编结果

图1-3 对比反汇编结果
说明 :生成二进制时,我们倾向于尽量减少跳转。代码中if (a[i] < a[j])表达式为真或假的概率影响了我们将交换等指令放置位置的决策,在一些性能瓶颈在frontend_bound(icache miss, branch miss较大)的大型应用,这些决策可能会成为影响性能的主要因素。 具体来看,上图中的汇编片段对应swap_int所在的循环,两个str指令对应swap_int函数的实现,外层的cmp和sub指令对应if (a[i] < a[j]) 的判断和其外层的循环。由于数组中原始数据已经升序排列,源码中的if (a[i] < a[j]) 有很大概率为真。故cmp, swap_int函数和sub指令会被连续执行,经过PGO优化后他们被放在了连续的指令流中,而未开启PGO优化之前还需要一个额外的跳转才能回到sub指令。由此可见PGO优化开启后,编译器根据程序的运行时分支概率信息对汇编指令进行了更优的排列。 这里例子中,读者可以试试将 if (a[i] < a[j]) 修改为 if(__builtin_expect(!!(a[i] < a[j]), 1)),辅助编译器做分支预测,或许可以取得与 PGO 相似的性能。 同时同学们还可以去体验我们毕昇编译器的沙箱实验https://www.hikunpeng.com/learn/experiments/detail/e280a5eae9c845b0a48e923c9c7cbe1f 更进一步了解和掌握毕昇编译器的使用,学习到更多的新知识,祝大家学习顺利,收获满满! 欢迎大家在社区中发帖,讲一讲自己使用DevKit迁移工具的过程与心得,给我们后来的小伙伴们提供一些指导与帮助。
【温馨提醒】 完成体验过程的小伙伴可以回到云开发服务资源申请页面,点击释放资源,将服务器资源留给后续的同学进行体验。在申请时间内没有完成体验的同学可以点击续约按钮延长服务器资源的使用时间。
各位同学大家好,欢迎大家参加毕昇编译器的体验活动,我是本期活动的分享嘉宾。接下来我将给大家分享一下如何使用毕昇编译器进行 PGO反馈优化 。
PGO 介绍
PGO 是 Profile Guided Optimization 的缩写,即档案导引优化。是一种自适应优化手段,不需要对软件代码本身做出改进,即可获得性能的提升,目前已经应用于GCC、VC++、Clang等常见的编译器中。
配置文件信息可以帮助编译器更好地优化。例如,知道分支被非常频繁地执行,有助于编译器在 排序基本块 时做出更好的决策;知道函数foo的调用频率比另一个函数条的调用频率更高,有助于 内联 。此外,配置文件还能指导编译器更好的进行 冷热分区 、 函数分区 、 寄存器分配 、 循环展开和剥离 、 增强代码和数据的局部相关性、Switch语句扩展 (建议使用-O2以上的优化等级使用PGO)。
毕昇支持两种类型的PGO采样。一种是使用采样分析器,可以在牺牲很小运行时开销情况下生成配置文件。二是重新构建代码获取一个插桩版本的二进制以收集更详细的配置文件信息。这两种配置文件都可以提供代码中指令的执行计数,以及关于所采取的分支和函数调用的信息。
使用时需要注意,插桩编译和反馈编译时的源代码要完全一致,有时编译选项也要一致,否则可能导致无法关联profile数据或做出糟糕的优化选择,最好是在代码通过了单元测试和回归测试后再使用。另外,若程序大部分时间花在函数调用和分支跳转时,PGO优化收益大,若大部分时间在循环和计算时,PGO收益较小。如图是PGO的优化过程:
图 1-1 PGO优化过程
步骤 1 准备工作
毕昇编译器的体验流程需要在鲲鹏平台上完成,因此我们需要申请鲲鹏远程实验室的服务器资源。这个时候有的同学可能会头疼了,我没有操作过服务器呀,应该怎么去做呀?通过黑框操作服务器好不方便,太麻烦了。 为了降低大家在鲲鹏平台上体验的使用门槛,我们推出了云开发环境,首先申请简单,输入邮箱地址后就可以申请到鲲鹏远程实验室资源,其次操作简单,申请完成后可以直接通过在线IDE的方式打开页面进行操作。整个体验就突出两个字,顺滑! 首先我们访问鲲鹏社区的远程实验室页面: https://www.hikunpeng.com/zh/developer/cloud-lab ,申请云开发环境:
之后我们输入自己的邮箱地址,选择 openEuler 20.03 LTS-SP3 系统,因为专项软件迁移功能体验过程相对简单,1天的体验时间完全足够,这样也方便后面体验的小伙伴快速申请到体验资源。申请成功后我们点击“打开在线IDE”按钮,进入云开发环境中,云开发环境给大家提供了在线 Vscode 页面,方便我们直接在鲲鹏上进行开发。
步骤 2 创建code.c文件,并将如下代码拷贝至code.c文件中
// code.c #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <assert.h> #include <unistd.h> #define ARRAY_LEN 80000 int data[ARRAY_LEN]; static struct timeval tm1; static inline void start() { gettimeofday(&tm1, NULL); } static inline void stop() { struct timeval tm2; gettimeofday(&tm2, NULL); unsigned long long t = 1000 * (tm2.tv_sec - tm1.tv_sec) + (tm2.tv_usec - tm1.tv_usec) / 1000; printf("Average: %llu ms\n", t); } void swap_int(int *a,int *b) { int c = *a; *a = *b; *b = c; } void bubble_sort(int *a, int n) { int i, t, s = 1; for (i = 0; i < n -1; i++) { for(int j=i+1;j<n-1;j++) if (a[i] < a[j]) { swap_int(&a[i],&a[j]); } } } void sort_array() { printf("Bubble sorting array of %d elements\n", ARRAY_LEN); bubble_sort(data, ARRAY_LEN); } void get_data() { for (int i = 0; i < ARRAY_LEN; ++i) { data[i] = i; } #ifdef __aarch64__ asm volatile ("DMB SY"); #endif } int main() { get_data(); start(); sort_array(); stop(); return 0; }步骤 3 获取未优化的情况
说明:默认使用-O0,是最安全,但也是性能最差的。
步骤 4 获取基准值
说明:相比于-O3的一些激进不稳定的优化,-O2是应用程序被推荐的优化等级,可以使用很多已知且稳定的优化。
步骤 5 测试 PGO 效果
说明:相比于步骤2的结果,这个更进一步得拿到了一些收益。
步骤 6 对比使用PGO优化前后热点情况
图1-2 对比使用PGO优化前后热点情况
说明:使用topdown工具分析,我们能够知道这个应用的瓶颈主要在 backend_bound, 这里抓了 cycles 事件,可以看到优化后 cycles数量有明显的下降。同时我们也能看到,优化前对内联函数的策略比较激进,热点函数在main, 优化后 sort_array函数没有内联,这种指导函数内联的优化,在一些 frontend_bound 很高的应用有很好的效果。
步骤 7 对比使用PGO优化前后冒泡排序函数bubble_sort的反汇编结果
图1-3 对比反汇编结果
说明 :生成二进制时,我们倾向于尽量减少跳转。代码中if (a[i] < a[j])表达式为真或假的概率影响了我们将交换等指令放置位置的决策,在一些性能瓶颈在frontend_bound(icache miss, branch miss较大)的大型应用,这些决策可能会成为影响性能的主要因素。 具体来看,上图中的汇编片段对应swap_int所在的循环,两个str指令对应swap_int函数的实现,外层的cmp和sub指令对应if (a[i] < a[j]) 的判断和其外层的循环。由于数组中原始数据已经升序排列,源码中的if (a[i] < a[j]) 有很大概率为真。故cmp, swap_int函数和sub指令会被连续执行,经过PGO优化后他们被放在了连续的指令流中,而未开启PGO优化之前还需要一个额外的跳转才能回到sub指令。由此可见PGO优化开启后,编译器根据程序的运行时分支概率信息对汇编指令进行了更优的排列。 这里例子中,读者可以试试将 if (a[i] < a[j]) 修改为 if(__builtin_expect(!!(a[i] < a[j]), 1)),辅助编译器做分支预测,或许可以取得与 PGO 相似的性能。 同时同学们还可以去体验我们毕昇编译器的沙箱实验https://www.hikunpeng.com/learn/experiments/detail/e280a5eae9c845b0a48e923c9c7cbe1f 更进一步了解和掌握毕昇编译器的使用,学习到更多的新知识,祝大家学习顺利,收获满满! 欢迎大家在社区中发帖,讲一讲自己使用DevKit迁移工具的过程与心得,给我们后来的小伙伴们提供一些指导与帮助。
【温馨提醒】 完成体验过程的小伙伴可以回到云开发服务资源申请页面,点击释放资源,将服务器资源留给后续的同学进行体验。在申请时间内没有完成体验的同学可以点击续约按钮延长服务器资源的使用时间。