计算机系统结构总结_Cache Optimization
Textbook:
《计算机组成与设计——硬件/软件接口》 HI
《计算机体系结构——量化研究方法》 QR
Ch4. Cache Optimization
本章要讨论的问题就是 How to Improve Cache Performance?
前面讲过 Average memory access time = HitTime + (MissRate * MissPenalty)
那么我们的方向就是Reduce MissRate / HitTime / MissPenalty
1. 6 Basic Cache Optimization(PPT P3)
• Reducing hit time
1. Giving Reads Priority over Writes
• E.g., Read complete before earlier writes in write buffer ??
2. Avoiding Address Translation during Cache Indexing
Cache中使用虚拟地址,这样就可以同时Access TLB和Cache / Access Cache firstly
• Reducing Miss Penalty
3. Multilevel Caches
AMAT = Hit TimeL1 + Miss RateL1 x Miss PenaltyL1
Miss PenaltyL1 = Hit TimeL2 + Miss RateL2 x Miss PenaltyL2
原来Miss PenaltyL1要访问内存,很慢。现在多了L2
• Reducing Miss Rate
4. Larger Block size (Compulsory misses)
...
5. Larger Cache size (Capacity misses)
...
6. Higher Associativity (Conflict misses)
...
2. 11 Advanced Cache Optimizations (PPT P12)
• Reducing hit time
1. Small and simple caches(QR P59)
如果仅考虑Cache Hit Time,那么结构越简单、容量越小、组相连路数越少的缓存肯定是越快的。
所以出于速度考虑,CPU的L1缓存都是很小的。比如从Pentium MMX到Pentium 4,L1缓存的容量都没有增长。
不过太少了肯定也是不行的。。。所以这也是一个Trade off
2. Way prediction
直接相连的Hit Time是很快的,但conflict miss多。组相连可以减少conflict miss,但结构复杂功耗也高一些,hit time也多一点。那么有什么方法能两者兼得呢?
因为组相连缓存中,每一个组里面的N个路(block)是全相连的。也就是相当于读的时候,每次映射好一个set之后,要遍历一遍N个block,当N越大的时候费的时间就越多。有一种黑科技方法叫做路预测(way prediction),它的思想就是在缓存的每个块中添加预测位,来预测在下一次缓存访问时,要访问该组里的哪个块。当下一次访问时,如果预测准了就节省了遍历的时间(相当于直接相连的速度了);如果不准就再遍历呗。。。
好在目前这个accuracy还是很高的,大概80+%了。
不过有个缺点就是Hit Time不再是确定的几个cycle了(因为没命中的时候要花的cycle多嘛),不便于后面进行优化(参考CPU pipeline)。
3. Trace caches
这个只针对instruction cache。在读取指令缓存时,要不断jump来读取不同的指令(也就是比较random的Access pattern),这样就不如sequential的access快了。
在牙膏厂的Pentium 4中,使用了trace caches的黑科技。它会尝试找出相邻被访问的指令(比如A jump to B),然后把这些block放到邻近的位置,这样就可以access instruction cache sequentially。
但因为现在code reuse rate不高了(程序太多了,很多程序可能一段时间内只执行一次),再加上这个黑科技implement比较复杂,后来就放弃了。
• Increasing cache bandwidth
4. Pipelined caches
在本科的计组课上我们学过pipeline的思想。在cache访问中也可以使用pipeline技术。
但pipeline是有可能提高overall access latency的(比如中间有流水线气泡),而latency有时候比bandwidth更重要。所以很多high-level cache是不用pipeline的
5. cache with Multiple Banks
对于Lower Level Cache(比如L2),它的read latency还是有点大的。假设我们有很多的cache access需要访问不同的数据,能不能让它们并行的access呢?
可以把L2 Cache分成多个Bank(也就是多个小分区),把数据放在不同Bank上。这样就可以并行访问这几个Bank了。
那么如何为数据选一个合适的Bank来存呢?一个简单的思路就是sequential interleaving:Spread block addresses sequentially across banks. E,g, if there 4 banks, Bank 0 has all blocks whose address modulo 4 is 0; bank 1 has all blocks whose address modulo 4 is 1; ...... 因为数据有locality嘛,把相邻的块存到不同bank,就可以尽量并行的访问locality的块了
6. Nonblocking caches
假设要执行下面一段程序:
Reg1:=LoadMem(A);
Reg2:=LoadMem(B);
Reg3:=Reg1 + Reg2;
当执行第一行时,cpu发现地址A不在cache中,就需要去内存读。但读内存的时间是很长的,此时CPU也不会闲着,就去执行了第二行。然后发现B也不在cache中。那么此时cache会怎么做呢?
- (a). cache阻塞,等着先把A读进来,然后再去读B。这种叫做Blocking Cache
- (b). cache同时去内存读B,最终B和A一起进入Cache。这种叫做Non-Blocking Cache
可以看出Non-Blocking Cache应该是比较高效的一种方法。在这种情况下,两条语句的总执行时间就只有一个miss penalty了:

(图中只是大概的描述,不是精确的时间计算。。。如果用了上面介绍的multiple bank cache,那么hit时间可能也只需要一次了,很棒棒吧!)
• Reducing Miss Penalty
7. Early Restart and Critical word first
相对一个Word来说,cache block size一般是比较大的。有时候cpu可能只需要一个block中的某一个word,那么如果cpu还要等整个block传输完才能读这个word就有点慢了。因此我们就有了两种加速的策略:
- Critical Word First:首先从存储器中读想要的word,在它到达cache后就立即发给CPU。然后在载入其他目前不急需的word的同时,CPU就可以继续运行了
- Early Restart:或者就按正常顺序载入一整个block。当所需的word到达cache后就立即发给CPU。然后在载入其他目前不急需的word的同时,CPU就可以继续运行了
大概就是这个意思:

根据locality的原理,一般来说CPU接下来要访问的也就是这个block中的剩余内容。所以没毛病!
8. Merging write buffers
?????(QR P65)
• Reducing Miss Rate
9. Compiler optimizations
这是最喜闻乐见的一种方法了hhhh
这里的reducing miss rate又可以分为Instruction miss和data miss两类:
Instruction Miss:
• Reorder procedures in memory so as to reduce conflict misses
• Profiling to look at conflicts(using tools they developed) (之前面试还被问到过Linux profiling了......)
Data Miss:这个是比较重要的一种方式了。网上很多大神所说的黑科技优化C代码的原理就是这个。
- 1. Merging Arrays: improve spatial locality by single array of compound elements vs. 2 arrays
假设有下面两个定义(他们的功能都是一样的,只是写法不同):
/* Before: 2 sequential arrays */
int val[SIZE];
int key[SIZE]; /* After: 1 array of stuctures */
struct merge {
int key;
int val;
};
struct merge merged_array[SIZE];
我们可以比较一下对于这两种定义方式,它们在内存中的组织方式:

好的现在我们要对index k,分别访问key[k]和val[k]。
/* Before: Miss Rate = 100% */
int k=rand(k);
int _key=key[k];
int _val=val[k]; /* After: Miss Rate = 50% */
int k=rand(k);
int _key=dat[k].key;
int _val=dat[k].val;
可以看出第二种方式充分利用了spatial locality。对于同一个index k,读取key_k的同时,val_k也被读进cache啦,这样就节省了一次访问内存的时间。
上面这个还可以引申出另一个话题,叫做结构体对齐。
- 2. Loop Interchange: change nesting of loops to access data in order stored in memory
还是下面两种程序,它们只是循环次序改变了:
int x[][]; //very large
//Assume a cacheline could contain 2 integers. /* Before */
for (j = ; j < ; j = j+)
for (i = ; i < ; i = i+)
x[i][j] = * x[i][j];
/* After */
for (i = ; i < ; i = i+)
for (j = ; j < ; j = j+)
x[i][j] = * x[i][j];
我们知道在C语言中,二维数组在内存中的存储方式是Row Major Order的,也就是这样:

那么对于第一种写法,访问顺序是x[0][0], x[1][0], x[2][0], ......。Miss Rate达到了100%
第二种写法,访问顺序是x[0][0], x[0][1], x[0][2], x[0][3], ......。读x[0][0]的时候可以把x[0][1]也读进来,读x[0][2]的时候可以把x[0][3]也读进来,以此类推。这样Miss Rate就只有50%啦
- 3. Loop Fusion: Combine 2 independent loops that have same looping and some variables overlap
来看个例子:
/* Before */
for (i = ; i < N; i = i+)
for (j = ; j < N; j = j+)
a[i][j] = /b[i][j] * c[i][j];
for (i = ; i < N; i = i+)
for (j = ; j < N; j = j+)
d[i][j] = a[i][j] + c[i][j]; /* After */
for (i = ; i < N; i = i+)
for (j = ; j < N; j = j+){
a[i][j] = /b[i][j] * c[i][j];
d[i][j] = a[i][j] + c[i][j];
}
在第二种写法中,line 13已经把a[i][j]和c[i][j]读进cache了,line14就可以接着用了。加起来比第一种要省很多cache miss。
不过第一种写法本身时间复杂度也高啊。。。这样写代码会被人打的。。。
emmm上面这个例子比较弱智。。。下面再来看一个经典的Matrix Multiplication的例子:
假设我们要计算一个大矩阵的乘法,然后cache block是4个integer的大小。
矩阵乘法是三重循环,O(N^3)的。我们来分析不同的循环顺序下,最内层循环的cache miss情况(因为cache很小,只会在最内层循环起作用,外面的肯定都要有miss的):

- 4. Blocking: Improve temporal locality by accessing “blocks” of data repeatedly vs. going down whole columns or rows
从上面的例子中可以看到,当每次access的是同一column中的不同row(a[1][3], a[2][3], a[3][3], a[4][3], ......),而不是同一row的不同colum时,miss rate是很可怕的。那么怎么避免这一现象呢?
一种思路是我们把整个大矩阵分解成若干个小矩阵(以所需的数据能被cache全部装下为标准),然后每次都把这个小块内要计算的任务全部完成,这样就不用access whole column了。
/* Before */
for (i = ; i < N; i = i+)
for (j = ; j < N; j = j+){
r = ;
for (k = ; k < N; k = k+)
r = r + y[i][k]*z[k][j];
x[i][j] = r;
} /* After */
for (jj = ; jj < N; jj = jj+B)
for (kk = ; kk < N; kk = kk+B)
for (i = ; i < N; i = i+)
for (j = jj; j < min(jj+B-,N); j = j+){
r = ;
for (k = kk; k < min(kk+B-,N); k = k+){
r = r + y[i][k]*z[k][j];
}
x[i][j] = x[i][j] + r;
}
其中B叫做Blocking Factor。(QR P67)
• Capacity Misses from 2N3 + N2 to 2N3/B +N2
• Conflict Misses Too?(没讲)
Blocking Transformation
其实前面提到的这些access pattern现在已经可以被compiler自动优化了,所以也算是上古时代的黑科技了......
• Reducing miss penalty or miss rate via parallelism
10. Hardware prefetching
假设cache block只能装下一个int,然后我们有如下指令:
int a[];
load a[];
load a[];
load a[];
load a[];
load a[];
load a[];
那么与其每次都cache miss重新载入,不如在第一次cache miss(load a[0])时,让cache预测到接下来会用到a[1], a[2], a[3], ......,然后提前载入到next level cache里备用。这就是硬件的prefetching。
对于Instruction Prefetching,CPU fetches 2 blocks on a miss: the requested block and the next consecutive block.(Requested block is placed in instruction cache when it returns, and prefetched block is placed into instruction stream buffer)
对于Data Prefetching,Pentium 4 can prefetch data into L2 cache from up to 8 streams from 8 different 4 KB pages. Prefetching invoked if 2 successive L2 cache misses to a page, or if distance between those cache blocks is < 256 bytes.
但hardware prefetching只对比较predictable的access pattern(特别是instruction prefetching)起作用。如果是访问一个动态链表那就不管用了......
11. Compiler prefetching
????(QR P69)
最后是对这些cache optimization的一个总结(QR P72):

...
计算机系统结构总结_Cache Optimization的更多相关文章
- 【5分钟+】计算机系统结构:CPU性能公式
计算机系统结构:CPU性能公式 基础知识 CPU 时间:一个程序在 CPU 上运行的时间.(不包括I/O时间) 主频.时钟频率:CPU 内部主时钟的频率,表示1秒可以完成多少个周期. 例如,主频为 4 ...
- 计算机系统结构总结_Multiprocessor & cache coherence
Textbook:<计算机组成与设计——硬件/软件接口> HI<计算机体系结构——量化研究方法> QR 最后一节来看看如何实现parallelism 在多处 ...
- 计算机系统结构总结_Branch prediction
Textbook:<计算机组成与设计——硬件/软件接口> HI<计算机体系结构——量化研究方法> QR Branch Prediction 对于下面的指令: ...
- 计算机系统结构总结_Scoreboard and Tomasulo
Textbook:<计算机组成与设计——硬件/软件接口> HI<计算机体系结构——量化研究方法> QR 超标量 前面讲过超标量的概念.超标量的目的就是实现指 ...
- 计算机系统结构总结_Instruction Set Architecture
Textbook:<计算机组成与设计——硬件/软件接口> HI<计算机体系结构——量化研究方法> QR 这节我们来看CPU内部的一些东西. Instruct ...
- 计算机系统结构总结_Memory Hierarchy and Memory Performance
Textbook: <计算机组成与设计——硬件/软件接口> HI <计算机体系结构——量化研究方法> QR 这是youtube上一个非常好的memory syst ...
- 计算机系统结构总结_Memory Review
这次就边学边总结吧,不等到最后啦 Textbook: <计算机组成与设计——硬件/软件接口> HI <计算机体系结构——量化研究方法> QR Ch3. Memor ...
- 计算机体系结构——CH1基本概念
CH1基本概念 右键点击查看图像,查看清晰图像 CH1基本概念 目的与内容 了解计算机系统的完整概念 学习计算机系统的分析方法与设计方法 编写程序所必需了解的计算机属性 计算机系统结构简介 为什么要研 ...
- Linux Barrier I/O 实现分析与barrier内存屏蔽 总结
一直以来.I/O顺序问题一直困扰着我.事实上这个问题是一个比較综合的问题,它涉及的层次比較多,从VFS page cache到I/O调度算法,从i/o子系统到存储外设.而Linux I/O barri ...
随机推荐
- MFC多文档获取窗口句柄
GET App AfxGetInstanceHandle() AfxGetApp() GET Frame->View->Document SDI AfxGetM ...
- jquery last 选择器 语法
jquery last 选择器 语法 作用: :last 选择器选取最后一个元素.最常见的用法:与其他元素一起使用,选取指定组合中的最后一个元素(就像上面的例子). 语法:$(":last& ...
- 【转】毛虫算法——尺取法
转自http://www.myexception.cn/program/1839999.html 妹子满分~~~~ 毛毛虫算法--尺取法 有这么一类问题,需要在给的一组数据中找到不大于某一个上限的&q ...
- 接口自动化request库入门
requests库7个主要方法 r= requsts.get(),主要属性: r.raise_for_status()方法内部判断r.status_code是否等于200不需要增加额外的if语句,该语 ...
- shell基础操作
一.字符串 字符串是shell编程中最常用的数据类型,字符串可以用单引号,也可以用双引号,也可以不用引号. 单引号 name='xiaoxi' 单引号的限制: 单引号里的任何字符都会原样输出,单引号中 ...
- javascript 链式调用+构造函数
前几天面试,有一个问题是使用构造函数实现链式调用,后面查看了一些简单的资料,整理一下 首先,先说一下JS 中构造函数和普通函数的区别,主要分为以下几点 1.构造函数也是一个普通函数,创建方式和普通函数 ...
- json根据一个值返回对象,filter方法使用
d = { "student":[ { "count":1000, "stuList":[ ...
- Type.MakeGenericType 方法 (Type[]) 泛型反射
替代由当前泛型类型定义的类型参数组成的类型数组的元素,并返回表示结果构造类型的 Type 对象. 命名空间: System程序集: mscorlib(mscorlib.dll 中) public ...
- 3、maven导入外部自定义jar包
有些时候我们自己有一些jar包需要导入到我们的仓库中,然后在maven项目里的pom.xml文件加入这些jar包的依赖即可使用这些jar包了 1.确保行执行mvn -v没有问题 2.把需要引入的jar ...
- Linux_文件系统、磁盘分区_RHEL7
目录 目录 前言 文件系统 目录结构 文件的类型 文件系统损坏后的修复 磁盘分区 分区的类型 分区最小存储单元 查看当前分区的block的大小 分区格式 MBR格式 GPT格式 mount挂载指令 挂 ...