对于软件的缓存访问问题进行优化的第一步应该是选择合适的编译器选项,使得编译器能够根据你的应用和要针对的处理器进行优化。每个处理器采用不同的缓存,比如通过QxW针对P4处理器进行优化,/O3允许一些循环分割、合并等激进优化,/Qipo通过过程间优化可以减少代码的大小,通过代码移动优化使得经常调用的变量和函数可以放在一起,增加了空间的局部性,采用Profile导向的优化,可以知道哪些分支经常使用,提高指令缓存和数据缓存的利用率。
虽然编译器的功能非常强大,但是也不是万能的,只有程序员才知道程序的具体行为,有的时候程序员也可以手工修改代码来帮助编译器进行更进一步的优化。那些具有更好的时间局部性的空间和时间局部性的代码一般会有更低的缓存缺失率,从而在大部分情况下回比那些高缺失率的代码运行得更快。考虑到好的时间局部性,那些经常访问的数据可以通过一个局部变量来保存,考虑到空间局部性,步长为1的内存访问显然是最好的。大部分的缓存优化都是针对那些访问数组元素的循环的,而提高这些循环的局部性,需要考虑循环访问数组的模式,从而分析是什么原因引起的缓存缺失,然后通过改变循环的顺序、结构、数组的布局等来减少缓存的缺失。
示例1:考虑下面的代码:
点击(此处)折叠或打开
1.int sumarrarcols(int a[M][N])
2.{
3.int i,j,sum=0;
4.for(j=0;j<N;j++)
5.  for(i=0;i<M;i++)
6.    sum+=a[i][j];
7.
8.return sum;
9.}
由于C语言的数组是按照行来进行存储的,也就是a[0][0],a[0][1],...a[0][N-1],a[1][0],a[1][1],....,而sumarrarcols则是按照列顺序访问的,也就是a[0][0],a[1][0],a[2][0],...,a[M-1][0],a[0][1],a[1][1]...,步长为M,这些不连续的元素可能会位于不同的缓存行,会出现较多的强制性缺失,因此在优化时可以通过循环交换来改变循环的顺序来提高空间的局部性。
点击(此处)折叠或打开
1.int sumarraryrows(int a[M][N])
2.{
3.int i,j,sum=0;
4.for(i=0;i<M;i++)
5.   for(j=0;j<N;j++)
6.       sum+=a[i][j];
7.
8.
9.return sum;
10.}
对于第二个函数,二维数组a求和访问的内存顺序与内存中的属性一致,步长为1,在第一次访问a[0][0]时会出现缓存缺失,但是因为内存中的数据是以缓存行为单位加载进缓存的,因此访问后面的元素就会缓存命中,知道数组元素属于另外衣蛾缓存行,这时再把下一个缓存行加载到缓存中。
示例2  考虑下面的代码:
点击(此处)折叠或打开
1.for(i=0;i<N;i++)
2.   a[i]=b[i]+1;
3.
4.for(i=0;i<N;i++)
5.   c[i]=b[i]*3;
两个循环都要访问数组b[i],可以把两个循环合并,这样可以通过时间局部性来减少对于数组b[i]的访问:
点击(此处)折叠或打开
1.for(i=0;i<N;i++)
2.{
3.       a[i]=b[i]+1;
4.       c[i]=b[i]*3;
5.}
示例3 考虑下面的代码:
点击(此处)折叠或打开
1.for(i=0;i<N;i++)
2.    {
3.           a[i]=b[i]+1;
4.           c[i]=b[i]*3;
5.           f[i] = g[i]+h[i];
6.    }
假设N非常大,采用4-way缓存,但是在循环里面需要访问六个数组,这些数组的地址间的距离比较大,可能会导致这些数组都映射为同一个组,那么在每次循环过程中后面两次对于数组的访问都会导致缓存缺失冲突,这时可以把循环分割成为两个,每次循环访问的数组不超过四个,从而减少冲突缺失:
点击(此处)折叠或打开
1.for(i=0;i<N;i++)
2.{
3.  a[i]=b[i]+1;
4.  c[i]=b[i]*3;
5. }
6.
7.for(i=0;i<N;i++)
8.{
9.    f[i] = g[i]+h[i];
10.}
如果要访问的对象占用的空间非常大,而缓存行相对来说比较小,这个时候对缓存的利用变得非常重要,看看下面的矩阵加法的运算代码.
示例4
点击(此处)折叠或打开
1.void add(int a[MAX][MAX],int b[MAX][MAX])
2.{
3.   int i,j;
4.   for(i=0;i<MAX;i++)
5.      for(j=0;j<MAX;j++)
6.         a[i][j] = a[i][j]+b[j][i];
7.}
在上面的循环中,对于a的访问步长为1,可以利用空间的局部性,但是b的访问是按列进行访问的,也就是b[0][i],b[1][i]...,步长为MAX,如果数组比较大,数组中的一行业大于缓存的大小,这样每次访问b都可能会出现缺失,要重新载入一个缓存行。循环块优化可以把数组分成一个个的小块数据,这个数据块可以容纳入缓存之中,在对于块缓存进行处理之后就可以完全丢弃,然后继续处理下一个数据,优化后的代码为:
点击(此处)折叠或打开
1.#define BS //block size is selected as the loop-blocking factor
2.void add(int a[MAX][MAX],int b[MAX][MAX])
3.{
4.  int i,j,ii,jj;
5.for(i=0;i<MAX;i+=BS)
6.   for(j=0;j<MAX;j+=BS)
7.      for(ii=0;ii<BS;ii++)
8.         for(jj=0;jj<BS;jj++)
9.            a[ii][jj]=a[ii][jj]+b[jj][ii];
10.}
除了通过改变循环的顺序和循环的结构来进行缓存优化之外,还可以通过选择合适的数据布局来进行优化,其基本原则就是把那些经常使用的数据放在一起,这样可以利用空间的局部性。比如有两个数组,每次访问一个数组时,总是要访问另外一个数组:
示例5:
点击(此处)折叠或打开
1.#define N 1024
2.int a[N],b[N];
3.for(i=0;i<N;i++)
4.  a[i]=Func(b[i]);
这个时候可以把两个数组合并起来,先定义一个结构体类型,然后再顶一个结构体数组:
点击(此处)折叠或打开
1.#define N 1024
2.struct ALL{int a,b};
3.struct ALL M[N];
4.for(i=0;i<N;i++)
5.  M[i].a = Func(M[i].b);
如果一个大的结构中只有少数几个字段会被经常访问,但是大部分字段的访问次数相对比较小,这个时候可以把原来的结构数组分割成多个数组,考虑下面的代码:
点击(此处)折叠或打开
1.typdef struct __LISY{
2.char key[8];
3.cahr val[48];
4.struct _LIST *next;
5.}List;
6.
7.List *lookup(char *key,List *head){
8.while(head != NULL){
9.if(strncmp(key,head->key,strlen(key))==0)
10.return head;
11.
12.head = head->next;
13.}
14.}
查找List的循环中只用到结构中的8个字节,但是val占用的字节相对比较大,这样按照顺序访问时容易出现缓存的浪费,代码可以优化为:
点击(此处)折叠或打开
1.typdef struct __LIST{
2.    cahr val[64];
3.    struct _LIST *next;
4.    }List;
5.    List list[MAX];
6.    char keylist[MAX*8];
7.    List *lookup(char *key,List *head,char *keyList,int nlist){
8.int i=0;
9.    while(i<nlist){
10.    if(strncmp(key,keylist,strlen(keylist))==0)
11.     return head[i];
12.    i++;
13.    headList += strlen(keyList)+1;
14.    }
15.    }
除了缓存缺失影响内存的访问速度之后,另外一个影响内存的访问速度的因素就是数据对齐。一般来说,一个对齐的数据的访问是最为有效的,这就要求变量的地址应该是能够被该变量占用的字节数整除。比如,一个double需要8个字节的存储空间,因此,如果double的地址不是8的倍数时,访问该变量会带来额外的延迟。值得注意的是,如果一个变量分配的空间跨越缓存行时,比如考虑变量的地址为0xFF0000FC,前面四个字节和后面四个字节在不同的缓存行,访问该变量需要加载进两个缓存行,这种跨越L缓存行(64B)边界的未对齐数据访问被称为载入分离或者存储分离。考虑下面的代码行:
double *p = (double *)malloc(sizeof(double)*number_of_doubles);
这行代码不能保证p的地址一定是8字节对齐的,这是因为malloc访问的变量会保证4字节对齐,那么访问double数据时的性能将会非常糟糕,上面的代码可以优化为:
double *p;
double *np;
p== (double *)malloc(sizeof(double)*(number_of_doubles+7L));
np = (double *)(((long )(p)+7L)&(-8L));
有时你可能希望大的对象或者数组能够与缓存行对齐,这是可以采用declspec来说明:
__declspec(align(64))
int BigArray[1024];
在使用多维数组时,维数不是2的幂时,可能会导致访问后面的数组元素出现不对齐的情况,考虑下面的代码:
int a[64][63];
for(i=0;i<64;i++)
for(j=0;j<63;j++)
a[i][j] = i*64 +j;
假设数组的起始位置是16字节对齐的,a[0][0],a[0][1],...,a[0][62]为16字节对齐,但是现在a[1][0]的距离16字节的边界差12字节,不是16字节对齐的,显然上面的代码可能通过填充来保证每个数组元素都是16字节对齐。牺牲了空间来换取对齐带来的内存访问的性能提升:
int a[64][64];
for(i=0;i<64;i++)
for(j=0;j<63;j++)
a[i][j] = i*64 +j;

Intel 平台编程总结----缓存的优化相关推荐

  1. 英特尔多核平台编程优化大赛报告

    前言 本次优化使用的CPU是Intel Xeon 5130 主频为2.0GHz 同Intel酷睿2一样是基于Core Microarchitecture 的双核处理器.本次优化在Intel的工具帮助下 ...

  2. 公开“英特尔多核平台编程优化大赛”优化报告及源代码

    公开"英特尔多核平台编程优化大赛"优化报告及源代码 本系列文章欢迎转载.打印.分发等,但不可用于商业用途,任何时候必须保留全文完整,并声明转载自恋花蝶的博客(http://blog ...

  3. 浅谈Android中的异步加载之ListView中图片的缓存及优化三

    隔了很久没写博客,现在必须快速脉动回来.今天我还是接着上一个多线程中的异步加载系列中的最后一个使用异步加载实现ListView中的图片缓存及其优化.具体来说这次是一个综合Demo.但是个人觉得里面还算 ...

  4. 硬件编解码开发 linux,Intel平台硬件加速视频编解码开发

    视频编解码分为硬件加速以及非硬件加速.硬件加速是指通过显卡,FPGA等硬件进行视频编解码,由于硬件有专门优化,所以性能高,能耗低,非硬件加速编解码是指通过CPU进行视频编解码,性能就没那么高(虽然有相 ...

  5. PHP服务缓存加速优化实战

    PHP服务缓存加速优化实战 (1) 操作码介绍及缓存原理: 当客户端请求一个PHP程序的时候,服务器的 PHP 引擎会解析该 PHP 程序,并将其编译为特定的操作码(Operate Code)文件. ...

  6. LNMP架构之PHP——MemCache对PHP页面的缓存加速优化

    前言 1.什么是MemCache? MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载. 它通过在内存中缓存数据和对象来减少读取数据库的 ...

  7. PAI分布式机器学习平台编程模型演进之路

    摘要: 在云栖计算之旅第5期-大数据与人工智能大会上,来自阿里云大数据事业部的九丰分享了<PAI分布式机器学习平台编程模型演进之路>.他主要介绍了在集团中使用机器学习解决大数据问题时如何通 ...

  8. mysql数据库前端缓存_MySQL数据库性能优化--缓存参数优化

    在平时被问及最多的问题就是关于 MySQL 数据库性能优化方面的问题,所以最近打算写一个MySQL数据库性能优化方面的系列文章,希望对初中级 MySQL DBA 以及其他对 MySQL 性能优化感兴趣 ...

  9. 微星主板超频_微星垄断AMD、Intel平台内存超频记录 ITX小板惊人

    微星主板日前又创造了Intel平台的超频世界记录了,将内存频率进一步推向DDR4-5902MHz,更让人惊讶的是这次还是在微星Z390I GAMING EDGE AC这样一块ITX小板上创造的. 微星 ...

最新文章

  1. golang 映射 map 简介
  2. 【数理知识】辛矩阵 symplectic
  3. 析构函数为什么写成虚函数?
  4. Linux 系统服务管理(启动服务/停止服务/重启服务)的命令 - chkconfig/service/systemctl
  5. android 打电话
  6. python from import什么意思_Python 引用From import介绍
  7. android q桌面,Android Q带来全新桌面模式
  8. 你们网贷逾期最长多少时间,你们怎么处理的
  9. ISOIEC27000标准族-ISO27001关联体系
  10. NOIP2016普及组T2(回文日期)题解
  11. c语言 随机生成数独,数独高效随机生成算法的研究与实现
  12. 数字信号处理——有限长离散变换
  13. win10计算机恢复到一天前,win10怎么系统还原到某一时刻 win10系统还原之后会怎么样...
  14. 生成package.json文件
  15. Fabric CA的基础知识
  16. 新存储、新格局、新飞跃,浪潮存储应时而来
  17. 【CCF CSP】【Python】【201903-1】小中大
  18. javaweb课程设计网上书店
  19. 辽宁启迪电商:拼多多店铺推广收费标准是什么?
  20. 罗振宇 知识就是力量:怎样成为一个不纠结的人

热门文章

  1. 光纤会在将来完全取代铜缆吗?
  2. 探讨打造会呼吸的数据中心
  3. 两个service事务统一_RocketMQ进阶 - 事务消息
  4. Java:Java的jar包之POI的简介、安装、使用方法(基于POI将Word、Excel、PPT转换为html)之详细攻略
  5. DayDayUp:今天早上看到一条朋友圈——《吃苦与穷的深刻认知》
  6. OS_CORE.C(5)
  7. rabbitMQ(二):Fanout Exchange
  8. [Intellij] 软件设置和常用快捷键
  9. Linux 下如何安装软件?
  10. 关于UNION ALL与 UNION 用法和区别