Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >《深入理解计算机系统》(CSAPP)实验六 —— Cache Lab

《深入理解计算机系统》(CSAPP)实验六 —— Cache Lab

作者头像
嵌入式与Linux那些事
发布于 2021-05-20 03:56:51
发布于 2021-05-20 03:56:51
6.7K00
代码可运行
举报
运行总次数:0
代码可运行

这是CSAPP的第6个实验,本实验将帮助我们了解缓存对C语言性能的影响。而且,这个实验比前几个难度都加大了,做实验前建议先去看24张图7000字详解计算机中的高速缓存,理解下Cache的基本原理。

1. 实验目的

  本次实验室由两部分组成。第一部分是要模拟Cahce的行为,理解Cache的原理。第二部分将优化一个小的矩阵转置功能,目的是最大程度地减少高速缓存未命中的次数。

2. 实验准备

  实验用到的所有文件在CSAPP官网都可以找到。我的运行环境Ubuntu 16.04,Gcc 5.4.0。

2.1 参考跟踪文件

  讲义目录的traces子目录包含参考跟踪文件的集合,我们将使用这些参考跟踪文件来评估在A部分中编写的缓存模拟器的正确性。跟踪文件由名为valgrind的Linux程序生成。例如,输入

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
valgrind --version # 检查有没有安装valgrind
sudo apt install valgrind # 没有安装的话执行这一步
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
linux> valgrind --log-fd=1 --tool=lackey -v --trace-mem=yes ls -l

  在命令行上运行可执行程序“ ls -l”,按其发生的顺序捕获其每个内存访问的跟踪,并在stdout上打印它们。

  Valgrind内存跟踪具有以下形式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
I 0400d7d4,8
M 0421c7f0,4
L 04f6b868,8
S 7ff0005c8,8

  每行表示一个或两个内存访问。每行的格式是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[space]operation address,size

  操作字段表示内存访问的类型:“ I”表示指令加载,“ L”表示数据加载,“ S”表示数据存储,“ M”表示数据修改(即数据加载后跟数据存储) 。每个“ I”之前都没有空格。每个“ M”,“ L”和“ S”之前总是有一个空格。地址字段指定64位十六进制内存地址。 size字段指定操作访问的字节数。

2.2 注意事项

3. PartA Cache simulator

3.1 说明

  在A部分中,我们要在csim.c中编写一个缓存模拟器,该模拟器以valgrind内存跟踪为输入,在该跟踪上模拟缓存的命中/未命中行为,并输出命中,未命中和逐出的总数。

  我们提供了参考缓存模拟器的二进制可执行文件,称为csim-ref,它可在valgrind跟踪文件上模拟具有任意大小和关联性的缓存行为。它使用LRU(最近使用)替换策略选择出需要的缓存行。

  参考模拟器采用以下命令行参数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Usage: ./csim-ref [-hv] -s <s> -E <E> -b <b> -t <tracefile>
-h:可选的帮助标志,用于打印使用情况信息
•-v:显示跟踪信息的可选详细标志
•-s <s>:设置的索引位数(S = 2s是设置的数量)
•-E <E>:关联性(每组行数)
•-b <b>:块位数(B = 2b是块大小)
•-t <tracefile>:要重播的valgrind跟踪的名称

  命令行参数基于CS:APP2e教科书第597页的符号(s,E和b)。例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
linux> ./csim-ref -s 4 -E 1 -b 4 -t traces/yi.trace
hits:4 misses:5 evictions:3

  详细模式下的相同示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
linux> ./csim-ref -v -s 4 -E 1 -b 4 -t traces/yi.trace
L 10,1 miss
M 20,1 miss hit
L 22,1 hit
S 18,1 hit
L 110,1 miss eviction
L 210,1 miss eviction
M 12,1 miss eviction hit
hits:4 misses:5 evictions:3

防止恶意转载 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_16933601/article/details/111590671

3.2 编程

3.2.1 getopt 和fscanf的使用

  PPT中给出了getopt 和fscanf的使用例程,直接拿来用就行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main(int argc, char** argv){
    int opt,x,y;
    /* looping over arguments */
    while(-1 != (opt = getopt(argc, argv, “x:y:"))){
    /* determine which argument it’s processing */
    switch(opt) {
    case 'x':
    x = atoi(optarg);
    break;
    case ‘y':
    y = atoi(optarg);
    break;
    default:
    printf(“wrong argument\n");
    break;
		}
	}
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FILE * pFile; //pointer to FILE object
pFile = fopen ("tracefile.txt",“r"); //open file for reading
char identifier;
unsigned address;
int size;
// Reading lines like " M 20,1" or "L 19,3"
while(fscanf(pFile,%c %x,%d”, &identifier, &address, &size)>0)
{
// Do stuff
}
fclose(pFile); //remember to close file when done
3.2.2 定义结构体

  讲义中告诉我们,不需要处理B,因此cache_line结构体中包括有效位,标记位,时间戳三个变量就够了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct{
	int valid_bits;
	unsigned  tag;
	int stamp;
}cache_line;
3.2.3 初始化Cache

  定义一个cache[S][E]大小的二维数组(using malloc). 这样cache就模拟好了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void init(){
	cache = (cache_line**)malloc(sizeof(cache_line*)*S);             //malloc cache[S][E]
	for(int i=0;i<S;i++)
		*(cache+i) = (cache_line*)malloc(sizeof(cache_line)*E);
	for(int i=0;i<S;i++){
		for(int j=0;j<E;j++){
			cache[i][j].valid_bits = 0;      // set all valid_bits is zero
			cache[i][j].tag = 0xffffffff;           //no address
			cache[i][j].stamp = 0;            //time is 0;		
		}	
	}
}
3.2.4 解析输入的指令

  先分析每个输入的指令应该被如何操作。如果是I,则不是数据操作,直接忽略。如果是L或者S,则需要进行一次hit-mis eviction检测,如果是M,则相当于先L再S,需要进行两次hit-miss- eviction检测。然后考虑hit-miss- eviction检测细节。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	while(fscanf(file," %c %x,%d",&operation,&address,&size)>0){
		switch(operation){
			case 'L':
				update(address);
				break;
			case 'M':
				update(address);
			case 'S':
				update(address);
				break;
		}
		time();
	}

首先需要对读取的地进有分析,读取的地址结构如下所示:

低b位表示 block偏移,本实验中不需要计算blk偏移。中间s位是 set index位,表示对那个行操作。其余t位是tag位。用于标明对应的line是否有效。我们需要对得到的地址进行如下操作,解析出t和s。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	unsigned s_address =(address>>b) & ((0xffffffff)>>(32-s));                //set`s index
	unsigned t_address = address>>(s+b);                                 //tag`s index
3.2.5 LRU策略

替换策略使用的是LRU的缓存替换策略。如果该SET存满了,我每次要找到TIMESTAMP最小的替换。为了方便,我把TIMESTAMP初始化为0,之后每个操作+1. 当TIMESTAMP = 0的时候就代表不VALID。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void time(){
	for(int i=0;i<S;i++){
		for(int j=0;j<E;j++){
			if(cache[i][j].valid_bits == 1)
				cache[i][j].stamp++;		
		}	
	}
}

	for(int i=0;i<E;i++){
		if(cache[s_address][i].stamp > max_stamp){
			max_stamp = cache[s_address][i].stamp;
			max_i = i;
		}
	}
3.2.6 更新高速缓存Cache

  cache的容量有限,当满的时候需要牺牲行(或者说驱逐某行),先遍历当前组,判断它满了没有,如何判断是否满,可以遍历所有的行,只要有一个有效位为0,(有效位的作用是说明该行是否存储了数据,通俗的理解就是是否为空)则该组未满。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//更新高速缓存cache
for(int i=0;i<E;i++){
	if(cache[s_address][i].valid_bits == 0){
		cache[s_address][i].tag = t_address;
		cache[s_address][i].valid_bits = 1;
		cache[s_address][i].stamp = 0;       //now ,this is load
		miss++;
		return;
	}
}
3.2.7 完整代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "cachelab.h"
#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <stddef.h>
typedef struct{
	int valid_bits;
	unsigned  tag;
	int stamp;
}cache_line;
char* filepath = NULL;
int s,E,b,S;           // s is set ,E  is line,each line have 2^b bits ,S is 2^s set
int hit=0,miss=0,eviction=0;
cache_line** cache = NULL;

void init(){
	cache = (cache_line**)malloc(sizeof(cache_line*)*S);             //malloc cache[S][E]
	for(int i=0;i<S;i++)
		*(cache+i) = (cache_line*)malloc(sizeof(cache_line)*E);
	for(int i=0;i<S;i++){
		for(int j=0;j<E;j++){
			cache[i][j].valid_bits = 0;      // set all valid_bits is zero
			cache[i][j].tag = 0xffffffff;           //no address
			cache[i][j].stamp = 0;            //time is 0;		
		}	
	}
}

void update(unsigned address){
	unsigned s_address =(address>>b) & ((0xffffffff)>>(32-s));                //set`s index
	unsigned t_address = address>>(s+b);                                 //tag`s index
	//判断tag为是否相等,是否命中
    for(int i=0;i<E;i++){
		if((*(cache+s_address)+i)->tag ==t_address){
			cache[s_address][i].stamp = 0;       //now ,this is used
			hit++;
			return;
		}
	}
    //更新高速缓存cache
	for(int i=0;i<E;i++){
		if(cache[s_address][i].valid_bits == 0){
			cache[s_address][i].tag = t_address;
			cache[s_address][i].valid_bits = 1;
			cache[s_address][i].stamp = 0;       //now ,this is load
			miss++;
			return;
		}
	}
    //暴力实现LRU策略
	int max_stamp=0;
	int max_i;
	for(int i=0;i<E;i++){
		if(cache[s_address][i].stamp > max_stamp){
			max_stamp = cache[s_address][i].stamp;
			max_i = i;
		}
	}
	eviction++;
	miss++;
	cache[s_address][max_i].tag = t_address;
	cache[s_address][max_i].stamp = 0;	
}
void time(){
	for(int i=0;i<S;i++){
		for(int j=0;j<E;j++){
			if(cache[i][j].valid_bits == 1)
				cache[i][j].stamp++;		
		}	
	}
}
int main(int argc,char *argv[])
{
	int opt;         
	while((opt = getopt(argc,argv,"s:E:b:t:")) !=-1){           //parse command line arguments
		switch(opt){
		case 's':
			s=atoi(optarg);
			break;
		case 'E':
			E=atoi(optarg);
			break;
		case 'b':
			b=atoi(optarg);
			break;
		case 't':
			filepath = optarg;
			break;
		}
	}
	S = 1<<s;
	init();
	FILE* file=fopen(filepath,"r");
	if(file == NULL){     // read trace file
		printf("Open file wrong");		
		exit(-1);
	}
	char operation;
	unsigned address;
	int size;	
	while(fscanf(file," %c %x,%d",&operation,&address,&size)>0){
		switch(operation){
			case 'L':
				update(address);
				break;
			case 'M':
				update(address);
			case 'S':
				update(address);
				break;
		}
		time();
	}
	for(int i=0;i<S;i++)                  //free cache[S][E]
		free(*(cache+i));
	free(cache);
	fclose(file);	                //close file	
    printSummary(hit,miss,eviction);
    return 0;
}

4. PartB Efficient Matrix Transpose

4.1 说明

  在B部分中,我们将在trans.c中编写一个转置函数,该函数将尽可能降低高速缓存未命中率。 设A表示矩阵, A i j {A_{ij}} Aij​表示第i行第j列的分量。 A的转置 表示为 A T {A^T} AT,其中, A i j = A j i T {A_{ij}} = A_{ji}^T Aij​=AjiT​。

  在trans.c中为提供了一个示例转置函数,用于计算转置N×M矩阵A并将结果存储在M×N矩阵B中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
char trans_desc[] = "Simple row-wise scan transpose";
void trans(int M, int N, int A[N][M], int B[M][N])

  示例的转置函数是正确的,但是效率很低,因为访问模式会导致相对许多缓存未命中。

  在B部分中,我们的工作是编写一个类似的函数,称为transpose_submit,该函数可最大程度地减少不同大小的矩阵之间的高速缓存未命中数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
char transpose_submit_desc[] = "Transpose submission";
void transpose_submit(int M, int N, int A[N][M], int B[M][N]);

不要更改transpose_submit函数的描述字符串(“Transpose submission”)。自动分频器搜索此字符串,以确定要评估分数的转置函数。

4.2 注意事项

  1. 代码必须在没有警告的情况下进行编译才能获得分数。
  2. 每个转置函数最多可以定义12个int类型的局部变量。
  3. 不允许使用long类型的任何变量或使用任何位技巧将多个以上的值存储到单个变量中来避开上一条规则。
  4. 转置函数不能使用递归。
  5. 如果选择使用辅助函数,则在辅助函数和顶级转置函数之间的某个时间堆栈上最多可以包含12个局部变量。例如,如果您的转置声明了8个变量,然后调用了一个使用4个变量的函数,然后调用了另一个使用2个变量的函数,则堆栈中将有14个变量,这将违反规则。
  6. 您的转置函数可能不会修改数组A。但是,您可以对数组B的内容做任何想做的事情。
  7. 您不允许在代码中定义任何数组或使用malloc的任何变体。

4.3 编程

4.3.1 32 * 32 矩阵

  第一个测试矩阵大小是 32 x 32 的。我们先来分析一下,一个 int 类型数字是 4 字节,cache 中一行 32 字节,可以放 8 个 int 。先用原来给的示例代码看一下 miss 数量。

  其实这个题目和之前的Perfom Lab有点像,想要降低不命中次数,需要提高函数的局部性,要么通过修改循环顺序来提高空间局部性,要么通过分块技术来提高时间局部性。

  从空间局部性来看,矩阵A的步长为1,所以空间局部性良好,而矩阵B的步长为N,空间局部性较差,并且无论我们怎么调整循环顺序,都无法改变,所以无法从空间局部性的角度来减少不命中次数。

  所以我们需要通过分块技术来优化时间局部性。题目已经给定了 cache 的参数 s = 5,b = 5 ,E = 1。那么 cache 的大小就是 32 组,每组 1 行, 每行可存储 32 字节的数据。而int类型为4字节,所以缓存中的每个数据块可以保存8个元素,由于矩阵是行优先存储的,所以相当于保存了A [0] [0]~A [0] [7],A矩阵转置后A [0] [0]~A [0] [7]对应的位置为B[0] [0]~B[7] [0],意味着需要8个高速缓存行(B也是行优先访问),分别保存B[0] [0]~B[0] [7]、B[1] [0]~B[1] [7]……。只有这样,每次取出一个Cache,才能得到充分的利用。

  由于32x32矩阵中,每一行有32个元素,则相邻两行间隔了3个高速缓存行,比如根据矩阵B的地址,其元素保存在高速缓存中是如下形式。

组号

元素

0

B[0] [0] ~B[0] [7]

1

B[0] [8] ~B[0] [15]

2

B[0] [16] ~B[0] [23]

3

B[0] [24] ~B[0] [31]

4

B[1] [0] ~B[1] [7]

  可以发现,我们想要的B[0] [0]~B[0] [7]和B[1] [0]~B[1] [7]之间还间隔了3个高速缓存行。而该高速缓存配置刚好能保存8行(每行8个int元素,32字节),所以我们设置分块技术的块大小为8,此时高速缓存中就保存了B[0] [0]~B[0] [7]到B[7] [0]~B[7] [7]的块,则在内侧的循环中,就能充分利用这些块后才会将其丢弃,减少了原始代码中由于缓存空间有限,而驱逐了后面要用的块。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (M == 32 && N == 32)
	{
		int i, j, m, n;
		for (i = 0; i < N; i += 8)
			for (j = 0; j < M; j += 8)
				for (m = i; m < i + 8; ++m)
					for (n = j; n < j + 8; ++n)
					{
						B[n][m] = A[m][n];
					}
	}

  运行下看下结果。降低到了 343 次,分块技术效果不错,但是距离满分还有一些差距。

  可以发现,A 和 B 中相同位置的元素是映射在同一 cache line 上的,但是因为我们做转置的话,并不会把 A 中某位置的元素放到 B 中相同的地方,除非在对角线上,因为下标是一样的,此时就会发生原地转置。譬如我们已经把 A 的第四行存进去了,但是当要写 B44 时,发生了冲突,第四行被换成 B 的,然后读 A 的时候又换成了 A 的,就多造成了两次 miss。

  这个时候可以使用一个简单的办法,因为除了循环需要的 4 个变量外我们还剩余 8 个自由变量可以用,正好可以存一个 cache line。以空间换时间,把一行一次性读完,减少冲突不命中。代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if(M == 32 && N == 32)
	{
		int i, j, k, v1, v2, v3, v4, v5, v6, v7, v8;
		for (i = 0; i < 32; i += 8)
			for(j = 0; j < 32; j += 8)
				for(k = i; k < (i + 8); ++k)
				{
					v1 = A[k][j];
					v2 = A[k][j+1];
					v3 = A[k][j+2];
					v4 = A[k][j+3];
					v5 = A[k][j+4];
					v6 = A[k][j+5];
					v7 = A[k][j+6];			
					v8 = A[k][j+7];
					B[j][k] = v1;
					B[j+1][k] = v2;
					B[j+2][k] = v3;
					B[j+3][k] = v4;
					B[j+4][k] = v5;
					B[j+5][k] = v6;
					B[j+6][k] = v7;
					B[j+7][k] = v8;
				}
	}
4.3.2 64 * 64矩阵

  这里同样使用分块技术进行优化,需要注意的是,当矩阵大小变为64x64时,矩阵中的每一行需要8个高速缓存行进行保存,使得高速缓存中只能保存4行的矩阵内容,如果我们还是使用块大小为8的分块技术,就会使得第5行和第1行冲突、第6行和第2行冲突等等,由此就会出现冲突不命中,所以我们只能设置块大小为4。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
else if (M == 64 && N == 64)
	{
		int i, j, x, y;
		int x1, x2, x3, x4, x5, x6, x7, x8;
		for (i = 0; i < N; i += 8)
			for (j = 0; j < M; j += 8)
			{
				for (x = i; x < i + 4; ++x)
				{
					x1 = A[x][j]; x2 = A[x][j+1]; x3 = A[x][j+2]; x4 = A[x][j+3];
					x5 = A[x][j+4]; x6 = A[x][j+5]; x7 = A[x][j+6]; x8 = A[x][j+7];
					
					B[j][x] = x1; B[j+1][x] = x2; B[j+2][x] = x3; B[j+3][x] = x4;
					B[j][x+4] = x5; B[j+1][x+4] = x6; B[j+2][x+4] = x7; B[j+3][x+4] = x8;
				}
				for (y = j; y < j + 4; ++y)
				{
					x1 = A[i+4][y]; x2 = A[i+5][y]; x3 = A[i+6][y]; x4 = A[i+7][y];
					x5 = B[y][i+4]; x6 = B[y][i+5]; x7 = B[y][i+6]; x8 = B[y][i+7];
					
					B[y][i+4] = x1; B[y][i+5] = x2; B[y][i+6] = x3; B[y][i+7] = x4;
					B[y+4][i] = x5; B[y+4][i+1] = x6; B[y+4][i+2] = x7; B[y+4][i+3] = x8;
				}
				for (x = i + 4; x < i + 8; ++x)
				{
					x1 = A[x][j+4]; x2 = A[x][j+5]; x3 = A[x][j+6]; x4 = A[x][j+7];
					B[j+4][x] = x1; B[j+5][x] = x2; B[j+6][x] = x3; B[j+7][x] = x4;
				}
			}
	}

  比如我们使用块大小为8,则不命中数目为4723,当修改块大小为4时,不命中次数为1179。

4.3.3 61 * 67矩阵

  这个相对 64 来说好想一点,因为不是正好的边边相等的矩形,所以不一定要用 8 分块,一个个试下来发现单纯分块的话,17 分块达到了最小的是 1950 次。但是这样很粗糙,我们还是用 8 分块去稍微对对角线做下操作,因为 32 x 32 最小的 miss 的方法和这边是一样的,而且写起来太多了,我们就用最简单的存变量的方式去做。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
else if(M == 61 && N == 67)
	{
		int i, j, v1, v2, v3, v4, v5, v6, v7, v8;
		int n = N / 8 * 8;
		int m = M / 8 * 8;
		for (j = 0; j < m; j += 8)
			for (i = 0; i < n; ++i)
			{
				v1 = A[i][j];
				v2 = A[i][j+1];
				v3 = A[i][j+2];
				v4 = A[i][j+3];
				v5 = A[i][j+4];
				v6 = A[i][j+5];
				v7 = A[i][j+6];
				v8 = A[i][j+7];
				
				B[j][i] = v1;
				B[j+1][i] = v2;
				B[j+2][i] = v3;
				B[j+3][i] = v4;
				B[j+4][i] = v5;
				B[j+5][i] = v6;
				B[j+6][i] = v7;
				B[j+7][i] = v8;
			}
		for (i = n; i < N; ++i)
			for (j = m; j < M; ++j)
			{
				v1 = A[i][j];
				B[j][i] = v1;
			}
		for (i = 0; i < N; ++i)
			for (j = m; j < M; ++j)
			{
				v1 = A[i][j];
				B[j][i] = v1;
			}
		for (i = n; i < N; ++i)
			for (j = 0; j < M; ++j)
			{
				v1 = A[i][j];
				B[j][i] = v1;
			}
	}

  最后结果为1905,也达到了要求。

5. 总结

  整个实验难度确实提升不少,刚开始看完书发现对Cache缓存的过程还是不理解,又回去看了下才来做题。网上也看下其他大佬写的代码。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/01/20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
宋宝华:深入理解cache对写好代码至关重要
3. CPU与设备(其实也可能是个异构处理器,不过在Linux运行的CPU眼里,都是设备,都是DMA)的cache同步问题
刘盼
2023/08/22
1.4K0
宋宝华:深入理解cache对写好代码至关重要
如何编程模拟计算机中的高速缓存
  3.本实验不涉及真实的数据读写,不需要考虑block的细节,每行只有一个block。
嵌入式与Linux那些事
2021/04/20
5220
多核环境下cache line的测试
前阵子接触到一道关于数组内部链表(多用于内存池技术)的数据结构的题, 这种数据结构能够比普通链表在cache中更容易命中, 理由很简单, 就是因为其在地址上是连续的(=.=!), 借这个机会, 就对cpu cache进行了一个研究, 今天做一个简单的分享, 首先先来普及一下cpu cache的知识, 这里的cache是指cpu的高速缓存. 在我们程序员看来, 缓存是一个透明部件. 因此, 程序员通常无法直接干预对缓存的操作. 但是, 确实可以根据缓存的特点对程序代码实施特定优化, 从而更好地利用高速缓存. 
猿人谷
2018/01/17
1.6K0
《深入理解计算机系统》(CSAPP)实验五 —— Perfom Lab
  本次实验主要处理优化内存密集型代码。图像处理提供了许多可以从优化中受益的功能示例。在本实验中,我们将考虑两种图像处理操作:旋转,可将图像逆时针旋转90o,平滑,可以“平滑”或“模糊”图片。
嵌入式与Linux那些事
2021/05/20
1.5K0
《深入理解计算机系统》(CSAPP)实验五 —— Perfom Lab
如何写出让同事无法维护的代码?
译文:http://coolshell.cn/articles/4758.html
Java技术栈
2019/06/21
5570
深入理解计算机系统系列【计算机系统漫游】
操作系统原理是计算机行业基本功,想要成为一名计算机领域的专业人士,必不可少要打好基础。最近打算重点读一读《深入理解计算机系统》这本书,回顾和提升自己对计算机和操作系统的理解。这是第一篇:【计算机系统漫游】。【计算机系统漫游】主要通过跟踪hello程序的生命周期来开始对系统的学习----从它被程序员创建开始,到在系统上运行,输出简单的消息,然后终止。本文将沿着这个程序的生命周期,简要地介绍一些逐步出现的关键概念、专业术语和组成部分。
用户1432189
2018/09/05
6180
深入理解计算机系统系列【计算机系统漫游】
计算机系统基础作业
void func(int *xptr, int *yptr, int *zptr);
一只胡说八道的猴子
2020/12/16
1.6K0
深入理解Cache工作原理
今天给大家分享一篇关于 Cache 的硬核的技术文,基本上关于Cache的所有知识点都可以在这篇文章里看到。
程序员小猿
2021/11/23
3980
CPU Cache与False Sharing
现代多核CPU会在每个核心上加上一个较小的SRAM高速缓存存储器称为:L1高速缓存,其中L1缓存由分为dcache数据缓存,icache指令缓存。在L1缓存的下级加一个较大的L2高速缓存, 然后会再L2之下加一个多核共享的L3高速缓存。它们之间的逻辑结构大概是这样的:
Orlion
2024/09/02
1530
CPU Cache与False Sharing
《深入理解计算机系统》(CSAPP)实验一 —— Data Lab
  首先去官网Lab Assignments获得实验相关的文件(也可以加我QQ获取教学视频、PPT等内容)在每个实验文件的README中都详细介绍了如何修改程序,编译程序等。建议仔细阅读,有不明白的可以留言,看到后会及时回复。
嵌入式与Linux那些事
2021/05/20
2.5K0
《深入理解计算机系统》(CSAPP)实验一 —— Data Lab
《深入理解计算机系统》(CSAPP)读书笔记 —— 第六章 存储器层次结构
  随机访问存储器( Random-Access Memory,RAM)分为两类:静态的和动态的。静态RAM(SRAM)比动态RAM(DRAM)更快,但也贵得多。SRAM用来作为高速缓存存储器。DRAM用来作为主存以及图形系统的帧缓冲区。
嵌入式与Linux那些事
2021/05/20
1.4K0
《深入理解计算机系统》(CSAPP)读书笔记 —— 第六章 存储器层次结构
深入理解计算机系统(第三版)/ CSAPP 杂谈,第9章:虚拟内存
所有程序共享内存资源,这容易造成很多问题。虚拟内存用于管理内存,协调各程序之间的内存占用和释放,但对程序来说无感知。 物理寻址流程:CPU 执行加载指令时,生成一个物理地址,通过内存总线传递给主存。主存取出物理地址对应的内存,并返回给 CPU,CPU 将其存放在寄存器中 虚拟寻址流程:CPU 执行加载指令时,生成一个虚拟地址,通过内存总线传递给主存,主存将其转换成物理地址。主存取出物理地址对应的内存,并返回给 CPU,CPU 将其存放在寄存器中。转换过程叫做地址翻译 address translation。
sickworm
2019/02/27
9540
《深入理解计算机系统》(CSAPP)实验四 —— Attack Lab
  在官网下载得到实验所需文件解压后会得到五个不同的文件。对六个文件简要说明如下所示。
嵌入式与Linux那些事
2021/05/20
1.3K0
《深入理解计算机系统》(CSAPP)实验四 —— Attack Lab
CPU体系结构之cache小结
What is cache? CPU缓存(Cache Memory)位于CPU与内存之间的临时存储器,它的容量比内存小但交换速度快。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访
刘盼
2022/07/21
1.3K0
CPU体系结构之cache小结
深入理解计算机系统(第三版)/ CSAPP 杂谈,第6章:储存器层次结构
SRAM 贵,稳定,集成度低,用于高速缓存存储器 DRAM 较便宜,不稳定,集成度高,需要定时重新读写和纠错码,用于主存和帧缓冲区 DRAM 的存储单元(超单元)以二元阵列排列而不是线性排列,这样可以节省管脚。请求某个超单元先发送行,此时会将行缓存到内部行缓冲区;然后发送列,此时将该行该列的超单元数据返回给请求者。传统的 DRAM 会将剩余的数据丢掉,而 FPM DRAM会缓存整行。这两种DRAM早就已经停产了,现在主流是 DDR3/4 SDRAM。 可擦写编程器 EEPROM 掉电数据不
sickworm
2019/02/27
8840
图解操作系统-cpu cache
为充分发挥各种器件优点,计算机存储数据的物理器件不会只选择一种,而是以CPU为核心,由内而外地组建一整套存储体系结构。它将各种不同的器件组合成一个体系,让各种器件扬长避短,从而形成一种快速、大容量、低成本的内存系统。
JavaEdge
2022/11/02
8730
图解操作系统-cpu cache
《深入理解计算机系统》阅读笔记--计算机系统漫游
《深入理解计算机系统》,这本书,我多次想要好好完整的读一遍,每次都是没有坚持下去,但是作为一个开发者,自己想要成为为数不多的大牛之一,所以打算这次把这本书完整的好好读一遍,并整理为相关的博客! 书的开头说了一句话:计算机系统是由硬件和系统软件组成,他们共同工作来运行应用程序。 我们通常接触更多的是应用程序级别的,很少关注系统以及系统和硬件的交互,但是如果自己能完全理解计算机系统以及它对应用程序的影响,那将会让我们在软件开发的路上走的更远,也同时可以避免很多问题的发生。 拿最简单的hello.c 程序来说,我
coders
2018/05/28
5110
《计算机系统2》学习笔记
对系统某部分的加速时,其对系统整体性能的影响程度取决于该部分工作的所占的比重和加速程度。
叶茂林
2023/07/30
2870
《计算机系统2》学习笔记
计算机组织结构(六) Cache
📚 文档目录 合集-数的二进制表示-定点运算-BCD 码-浮点数四则运算-内置存储器-Cache-外存-纠错-RAID-内存管理-总线-指令集: 特征- 指令集:寻址方式和指令格式 为什么需要 cac
Rikka
2022/01/11
1.2K0
计算机组织结构(六) Cache
《深入理解计算机系统》(CSAPP)读书笔记 —— 第一章 计算机系统漫游
  接下来的计划是补充下操作系统和计算机组成原理相关的知识。从《深入理解计算机系统》这本书开始吧,系统学习下《深入理解计算机系统》这本书,还有9个Lab可以做下,以便加深理解。初步计划一周一章(不知道行不行),争取在放寒假前做完这些。
嵌入式与Linux那些事
2021/05/20
7010
《深入理解计算机系统》(CSAPP)读书笔记 —— 第一章 计算机系统漫游
推荐阅读
相关推荐
宋宝华:深入理解cache对写好代码至关重要
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验