lab4 Cache Geometries 深入理解计算机系统——高速缓存

时间:2023-01-09 18:22:37

这个实验主要是将高速缓存命中的一点东西,意在告诉我们平常多注意这方面的东西。


不懂java的,所以只管C的部分。


You will do this several times, making small modifications to see what differences they make—how the choice of language affects performance and how effective the compiler can be at optimizing your code when you:

  • interchange the order of the i and j loops
  • uncomment the commented line
  • change the size of the array being copied from 2048 x 2048 to 4096 x 4096



#include <stdio.h>

int src[2048][2048];
int dst[2048][2048];

/* Copies the contents of one 2048-by-2048 array (src) into another (dst). */
int main(int argc, char* argv[])
{
	// declare all variables before the code (conform to an older C standard...)
	int rep;
	int i, j;

	for ( rep = 0; rep < 10; rep++ )
	{
		for ( i= 0; i < 2048;i++ )
		{
			for ( j= 0; j< 2048; j++ )
			{
				//src[i][j] = i * rep;
				dst[i][j] = src[i][j];
			}
		}
	}

	return 0;
}


要做的就是测试三种不同情况下的程序运行时间:


不优化 O2优化
原始程序 0.163 0.167
调换i,j顺序 1.332 1.305
加上src数组赋值 1.814 1.815
改变数组大小为4096 8.424 8.487

上诉结果都是在保持上一个改变的基础上得出的结果,也就是加上src数组赋值这一步骤时,i和j的顺序还是调换的。




  1. What are the source code differences among the two Java implementations?

由于不懂java,所以只能编译一下C的程序,对java的就暂时不管了。


2.Pick a single pair of results that most surprised you. What is it about the results that surprised you? (That is, from the 32 pairs of measurement results, pick one pair whose relationship is least like what you would have guessed.)

最大的震惊就是gcc的-O2优化什么用也没有啊,有时候甚至是反而比不优化的都要来的慢啊。真是……(看看当i,j调换的时候-o2和-o1的区别)


  3.[Optional extra credit] None of these programs appear to actually do anything, so one is tempted to optimize them by simply eliminating all code (resulting in an empty main()). Is that a correct optimization? Related to that, try compiling this C program, with and without optimization, and then time running it:

#include <stdio.h>

#define SIZE 1000000

int main() {
    int i, j, k;
    int sum = 1;

    for (i = 0; i < SIZE; i++) {
        for (j = 0; j < SIZE; j++) {
            for (k = 0; k < SIZE; k++) {
                sum = -sum;
            }
        }
    }

    printf("hello, world\n");

    return 0;
}
这个问题说main函数的参数会对程序产生速度上的影响,这个就一个栈的问题,会有什么影响?个人认为没有什么影响吧。

还有  这个程序确定个人PC跑的完?出这个题的耍人的吧。估计是要让计算机冬天煮鸡蛋,多散散热的……

Part II: Inferring Mystery Cache Geometries


 Your job is to fill in the function stubs in  cache-test-skel.c  which, when linked with one of these cache object files, will determine and then output the cache size, associativity, and block size. Some of the provided object files are named with this information (e.g.  cache_64c_2a_16b.o  is a 64 KB capacity, 2-way set-associative cache with 16B blocks)


程序如下:


#ifndef __MYSTERY_CACHE_H
#define __MYSTERY_CACHE_H

typedef unsigned long long addr_t;
typedef unsigned char bool_t;
#define TRUE 1
#define FALSE 0

/** Initializes the cache. This function must be called so that the
    cache can initialize its data structures, though the mystery
    caches will ignore the provided arguments (as their parameters are
    hard-coded). */
void cache_init(int size, int block_size);

/** Lookup an address in the cache. Returns TRUE if the access hits,
    FALSE if it misses. */
bool_t access_cache(addr_t address);

/** Clears all words in the cache (and the victim buffer, if
    present). Useful for helping you reason about the cache
    transitions, by starting from a known state. */
void flush_cache(void);

#endif



#include <stdlib.h>
#include <stdio.h>

#include "mystery-cache.h"

   Returns the size (in B) of each block in the cache.
*/
int get_block_size(void) {
<span style="white-space:pre">	</span>int count=0;
<span style="white-space:pre">	</span>flush_cache();
<span style="white-space:pre">	</span>access_cache(0);
<span style="white-space:pre">	</span>while(access_cache(count))
          <span style="white-space:pre">	</span>count++;
 <span style="white-space:pre">	</span>return count;	
}


/*
   Returns the size (in B) of the cache.
*/
int get_cache_size(int block_size) {
  unsigned long long count=0;
  unsigned int i=0;    
  while(1)
  {
      count=count+block_size;
      flush_cache();
      for(i=0;i<count;i=i+block_size)
      {
               access_cache(i);
      }
      if(!access_cache(0))
          return count-block_size;
  }
}

/*
   Returns the associativity of the cache.
*/
int get_cache_assoc(int cache_size) {
  /* YOUR CODE GOES HERE */
  int assoc=0;
  int i;
  while(1)
  {
    assoc++;
    for(i=0;i<=assoc;i++)
            access_cache(i*cache_size);
      if(!access_cache(0))
        return assoc;
  }
}

int main(void) {
  int size;
  int assoc;
  int block_size;

  
  cache_init(0,0);

  block_size=get_block_size();
  size=get_cache_size(block_size);
  assoc=get_cache_assoc(size);
  printf("Cache block size: %d bytes\n", block_size);
  printf("Cache size: %d bytes\n", size);
  printf("Cache associativity: %d\n", assoc);

  return EXIT_SUCCESS;
}

这道题主要让你用mystery-cache.h头文件里面的三个函数,来对4个高速缓存的的模拟文件进行分析,得到高速缓存的块大小,缓存总容量和高速缓存行的大小。

这里主要注意这个函数:

/** Lookup an address in the cache. Returns TRUE if the access hits,
    FALSE if it misses. */
bool_t access_cache(addr_t address);
这个函数是有两个作用,1.把address放入高速缓存。2.检查高速缓存中有没有address这个地址。

这个函数比较坑,主要是它会把在高速缓存中的地址也压入高速缓存。

解释下,就是当高速缓存的块是4的时候,地址0,1,2,3四个地址是一起的,是在一个高速缓存行里面的。假设高速换存一组有4行,当对一个空的高速缓存进行操作时(执行flush_cache()函数),执行access_cache(0),access_cache(1),access_cache(2),access_cache(3)时,此时高速缓存第0组的缓存行是:0,1,2,3

理论上来说,应该进行了那个操作之后,缓存行应该只有0,因为1,2,3三个数据都可以在0这个缓存行中访问到,他们是在一个块里面的。这感觉是这个高速缓存模拟文件的一个不好的地方。对后面那三个函数编写,很麻烦啊。


求解缓存块大小:

思路:假设块的大小为N,首先先把0放入缓存区中,则执行access_cache(0)~access_cache(N-1)的过程中,这些地址全都会缓存在在第0组中,由于有先把0放入了缓存区,所以在查询asscess_cache(add)的过程中(add>0&&add<N),会一直返回1.而N由于和前面的数字不是在一个块里面的,N会被缓存在第1组,而第一组本来是空的,读取N会有冷不命中,所以查询时会返回0,由此可以得出块的大小N。

int get_block_size(void) {
	int count=0;
	flush_cache();
       	access_cache(0);
      	while(access_cache(count))
         	count++;
  	return count;	
}



求解缓存区的总容量:

思路很简单,就是往缓存区里面一直存入地址,根据缓存区相关知识,当缓存区存满时,会删除一个缓存区中的地址,以放入新的地址。所以程序的编写按照这个思路,一直往缓存区中存入数据的过程中,一直查询0是否在缓存区中,如果0不在,则缓存区因为缓存区容量已满,把0删除。但是因为asscess_cache(add)函数的特性,所以每次asscess_cache(0)的时候会把0写入,所以要用一个比较麻烦的方法。就是每次查询完0之后,要清空缓存区,并且重新写入地址。

int get_cache_size(int block_size) {
  unsigned long long count=0;
  unsigned int i=0;    
  while(1)
  {
      count=count+block_size;
      flush_cache();
      for(i=0;i<count;i=i+block_size)
      {
               access_cache(i);
      }
      if(!access_cache(0))
          return count-block_size;
  }
}

求解缓存区高速缓存行的大小:

求解思路:由于已经求接触缓存区的总容量cache_size,所以可以知道地址cache_size,2*cache_size,3*cache_size……n*cache_size和0总是在同一个缓存组里面的。利用这个性质,可以比较容易的得到assoc的值。

首先,存入0的地址,然后依次存入cache_size,2*cache_size,3*cache_size……,在存入的过程中,查询0是否在缓存区中,如果0不在缓存区中,则第0组空间已满,这是最后的n*cache_size中的n,就是assoc的大小。这里需要注意的就是access_cache()函数的特性。和上面一个函数的过程一样,查询过了,就要清空缓存区,重新写入,重新查询。

int get_cache_assoc(int cache_size) {
  /* YOUR CODE GOES HERE */
  int assoc=0;
  int i;
  while(1)
  {
    assoc++;
    for(i=0;i<=assoc;i++)
            access_cache(i*cache_size);
      if(!access_cache(0))
        return assoc;
  }
}