perf优化cache实例

本文探讨了如何通过优化数据结构和循环顺序来减少C程序中的Cache Misses,以提升程序性能。通过使用perf工具监测,发现在数组元素重组和循环顺序改变后,Cache Misses数量显著下降,从而实现约20%的性能提升。文章还列举了矩阵相乘函数的不同实现方式,展示了循环顺序对Cache效率的影响。最后强调,理论分析结合性能调优工具如perf是优化程序的关键。
摘要由CSDN通过智能技术生成

本文,我们主要关心的是cache miss事件,那么我们只需要统计程序cache miss的次数即可。
使用perf 来检测程序执行期间由此造成的cache miss的命令是perf stat -e cache-misses ./exefilename,另外,检测cache miss事件需要取消内核指针的禁用(/proc/sys/kernel/kptr_restrict设置为0)。
1、数据合并

有两个数据A和B,访问的时候经常是一起访问的,总是会先访问A再访问B。
这样A[i]和B[i]就距离很远,如果A、B是两个长度很大的数组,那么可能A[i]和B[i]无法同时存在cache之中。
为了增加程序访问的局部性,需要将A[i]和B[i]尽量存放在一起。为此,我们可以定义一个结构体,包含A和B的元素各一个。这样的两个程序对比如下:

test.c
#define NUM 393216
int main(){
float a[NUM],b[NUM];
int i;
for(i=0;i<1000;i++)
add(a,b,NUM);
}

int add(int *a,int *b,int num){
int i=0;
for(i=0;i<num;i++){
*a=*a+*b;
a++;
b++;
}
}
test2.c
#define NUM 393216
typedef struct{
float a;
float b;
}Array;

int main(){
Array myarray[NUM];
int j=0;
for(j=0;j<1000;j++)
add(myarray,NUM);
}

int add(Array *myarray,int num){
int i=0;
for(i=0;i<num;i++){
myarray->a=myarray->a+myarray->b;
myarray++;
}
}

注:我们设置数组大小NUM为393216,因为cache大小是3072KB,这样设定可以让A数组填满cache,方便对比。 对比二者的cache miss数量:
为了对比,之后测试了大小都为39216的情况,证明确实越大越好

NUM=393216NUM=39216
可以看到,后者的cache miss数量相对前者有一定的下降,大致优化性能20%。
进一步,可以查看触发cach-miss的函数:
test程序的结果(含-g查看函数调用):
NUM=393216
test1test2perf stat:

可见,page-faults次数不变, 但misses和耗时比例却上升,说明add函数的cache-misses和耗时有一定下降

2、循环交换

C语言中,对于二维数组,同一行的数据是相邻的,同一列的数据是不相邻的。
如果在循环中,让依次访问的数据尽量处在内存中相邻的位置,那么程序的局部性将会得到很大的提高。
观察下面矩阵相乘的几个函数:

test_ijk:
void Muti( double A[][NUM],double B[][NUM],double C[][NUM],int n){
int i,j,k;
double sum=0;
for (i=0;i<n;i++)
for(j=0;j<n;j++){
sum=0.0;
for(k=0;k<n;k++)
sum+=A[i][k]*B[k][j];
C[i][j]+=sum;
}
test_jki:
void Muti( double A[][NUM],double B[][NUM],double C[][NUM],int n){
int i,j,k;
double sum=0;
for (j=0;j<n;j++)
for(k=0;k<n;k++){
sum=B[k][j];
for(i=0;i<n;i++)
C[i][j]+=A[i][k]*sum;
}
}
test_kij:
void Muti( double A[][NUM],double B[][NUM],double C[][NUM],int n){
int i,j,k;
double sum=0;
for (k=0;k<n;k++)
for(i=0;i<n;i++){
sum=A[i][k];
for(j=0;j<n;j++)
C[i][j]+=B[k][j]*sum;
}
}

考察内层循环,可以发现,不同的循环模式,导致的cache失效比例依次是kij、ijk、jki递增。

3、循环合并

在很多情况下,我们可能使用两个独立的循环来访问数组a和c。
由于数组很大,在第二个循环访问数组中元素的时候,第一个循环取进cache中的数据已经被替换出去,从而导致cache失效。
如此情况下,可以将两个循环合并在一起。
合并以后,每个数组元组在同一个循环体中被访问了两次,从而提高了程序的局部性。

实际情况下,一个程序的cache失效比例往往并不像我们从理论上预测的那么简单。
影响cache失效比例的因素主要有:数组大小,cache映射策略,二级cache大小,Victim Cache等,
同时由于cache的不同写回策略,我们也很难从理论上预估一个程序由于cache miss而导致的时间耗费。
真正在进行程序设计的时候,我们在进行理论上的分析之后,只有使用perf等性能调优工具,
才能更真实地观察到程序对cache的利用情况。

参考资料:
https://blog.csdn.net/sunshineywz/article/details/109044155

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值