CS:APP 第6章 存储器层次结构 作业

6.22

相当于求函数的最值,通过初中知识可以得到当时取到最值。

6.23

6.24

A

最好情况就是全部连续。

B

6.25

6.26

6.27

A

0x8a4~0x8a7,0x704~0x707

B

0x1238~0x123b

6.28

A

B

0x18f0~0x18f3,0xb0~0xb3

C

0xe34~0xe37

D

0x1bdc~0x1bdf

6.29

A

CO:0~1
CI:2~3
CT:4~11

B

6.30

A

B

CO:0~1
CI:2~4
CT:5~12

6.31

A

00111000 110 10

B

CO:0x2
CI:0x6
CT:0x38
命中:是
返回:0xeb

6.32

A

10110111 010 00

B

CO:0x0
CI:0x2
CT:0xb7
命中:否
返回:—

6.33

0x1788~0x178b,0x16c8~0x16cb

6.34

dst数组:
src数组:

本来手算的,脑子不清楚算麻了,干脆写了个程序跑,省事多了。

#include 
#include 
using namespace std;

int cache[2] = {-1, -1};

vector src, dst;

void print(vector &vs)
{
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
            cout << vs[i * 4 + j] << ' ';
        cout << endl;
    }
}

int main()
{
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
        {
            int bsrc = i, bdst = j + 4;
            if (cache[bsrc & 1] != bsrc)
                src.push_back("m"), cache[bsrc & 1] = bsrc;
            else
                src.push_back("h");
            if (cache[bdst & 1] != bdst)
                dst.push_back("m"), cache[bdst & 1] = bdst;
            else
                dst.push_back("h");
        }

    cout << "src:\n";
    print(src);
    cout << "dst:\n";
    print(dst);
}

dst的答案没转置,脑补一下就行了。

6.35

显然足够大,放得下所有元素,那么只有初始的未命中,剩下的全是命中了。

dst数组:
src数组:

6.36

A

B

C

D

不会,块与块之间已经不会互相冲突,不会出现抖动现象。

E

会,只有对块的第一次访问会不命中,当块增大,访问块的次数增多,会导致命中率上升。

6.37

我对答案的那哥们好像做错了,主要不一样的地方在于第一列的第二和第三行。
对于的情况来说,每次步长为,缓存为,能够放下次循环,而每次循环次数为次,后面的会覆盖之前缓存中的内容,因为是在一次内层循环中,缓存被全部换了一遍,外层循环只不过是在重复这个过程,所以不命中率为,和之前抖动的现象类似。
对于的情况,和上面一种类似,也是抖动,只不过有一个改进是每次取的一个矩阵,这样右边的两个元素会命中,所以不命中率下降到了。

对于右边的情况,我们可以看到没有做任何事情,单单改变了元素数量命中率就会上升。思考了一下出现这种现象的原因。每次步长为,块大小为,所以步长为块,需要访问次,而缓存一共有块,发现这两个数字互素,只有塞满缓存后才开始替换,只有第一个元素会不命中,所以不命中率大约为,感觉和哈希用素数取模是同一个道理,倒不如说直接映射缓存其实就是一个哈希表。

这位老哥把缓存的图画出来了,可以帮助理解。

6.38

A

B

C

6.39

A

B

C

连续访问个元素,只有第一个不命中。

6.40

A

B

C

6.41

6.42

6.43

6.44

运行结果:

Clock frequency is approx. 2496.0 MHz
Memory mountain (MB/sec)
        s1      s2      s3      s4      s5      s6      s7      s8      s9      s10     s11     s12     s13     s14    s15
128m    16431   9272    6687    4910    3933    3396    2824    2481    2251    2114    1980    1854    1723    1652    1605
64m     16924   9866    6896    4929    4066    3407    2899    2643    2390    2185    2037    1872    1767    1695    1655
32m     17100   10608   7226    5359    4410    3682    3178    2835    2614    2248    2245    2092    2011    1896    1852
16m     18372   11009   8467    6270    5125    4269    3758    3344    3115    2902    2488    2631    2523    2375    2465
8m      20366   13656   10551   7949    5321    4516    4272    3454    3772    3593    3684    3844    3848    3951    4227
4m      28843   20763   16701   13705   11511   9892    8512    7540    7754    7437    7167    6920    6751    6211    6108
2m      27698   21271   17533   13978   11617   10004   8760    7758    7421    7178    6906    6676    6496    6346    6241
1024k   27726   20927   17356   13963   11671   10034   8810    7794    7461    7179    6895    6662    6491    6332    6243
512k    27800   21148   17539   14195   11940   10234   8978    7929    7544    7264    7074    6919    6844    6824    6827
256k    27900   23239   21056   18338   16104   14107   12500   11044   10965   11262   11206   11973   11848   12114   13013
128k    28870   23503   23341   21706   19779   16891   15620   13797   13810   13827   12269   12598   11995   12456   12725
64k     29284   24951   24255   22923   20046   16704   15037   12991   12246   12505   12370   11771   12240   13217   23401
32k     32900   31361   30494   28638   28200   28279   27819   27631   27869   28591   32039   30973   27587   26792   29952
16k     31313   30337   29761   29894   28196   30973   27813   31170   26722   30973   28589   25426   26652   26548   27256

观察s15列,从下往上,寻找梯度下降最快的几个点,数据趋势大约是30000->13000->6200->1700,比较明显地下降了三次,分别对应三级缓存,分别大约是128KB1.0MB32MB。实际上查看任务管理器中三级缓存的数据,分别为:256KB1.0MB6.0MB,和预估的有差距。说明这种方式只能大概地估计缓存大小,并不准确。

6.45

一开始觉得没什么好提升的,后来看了提示,仔细分析缓存的访问,发现对于src的访问已经没有提升了,而对于dst的访问还有提升的空间。
因为dst是按照列访问的,原来的访问模式每次将一块加载到缓存中,然后只使用一次就离开去别的块了,所以我们可以将原矩阵分块,使得每次加载一块进入缓存后,多使用几次再去下一个循环,而这个分块的大小,是根据缓存中一行的大小来决定的,在不清楚数据的情况下只能多次尝试。
代码如下:

#include 
#include 
#include 
using namespace std;

const int N = 8192;
int dst[N][N], src[N][N];

void gen()
{
    mt19937 rng(time(0));
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            src[i][j] = rng();
}
void test(void (*fun)(int *, int *, int, int), int block = -1)
{
    clock_t st = clock();
    fun(&dst[0][0], &src[0][0], N, block);
    cout << clock() - st << '\n';
}

void transpose(int *dst, int *src, int dim, int block)
{
    for (int i = 0; i < dim; i++)
        for (int j = 0; j < dim; j++)
            dst[j * dim + i] = src[i * dim + j];
}

void transpose_optimized(int *dst, int *src, int dim, int block)
{
    for (int i = 0; i < dim - block + 1; i += block)
        for (int j = 0; j < dim - block + 1; j += block)
            for (int m = 0; m < block; m++)
                for (int n = 0; n < block; n++)
                    dst[(j + n) * dim + i + m] = src[(i + m) * dim + j + n];
    for (int ii = i; ii < dim; ii++)
        for (int jj = 0; jj < dim; jj++)
            dst[jj * dim + ii] = src[ii * dim + jj];
    for (int ii = 0; ii < i; ii++)
        for (int jj = j; jj < dim; jj++)
            dst[jj * dim + ii] = src[ii * dim + jj];
}

int main()
{
    gen();
    cout << "origin: ";
    test(transpose);

    for (int block = 2; block <= 16; block++)
    {
        cout << "block size= " << block << ": ";
        test(transpose_optimized, block);
    }
}

c++写了个测试程序,先用随机数填充了一个8192*8192的矩阵,然后对原代码和优化后的代码分别进行时间的测量得到结果:

origin: 984
block size= 2: 454
block size= 3: 326
block size= 4: 270
block size= 5: 275
block size= 6: 250
block size= 7: 246
block size= 8: 219
block size= 9: 220
block size= 10: 229
block size= 11: 260
block size= 12: 288
block size= 13: 327
block size= 14: 326
block size= 15: 336
block size= 16: 356

发现运行时间先减少再增大,这样的结果是意料之中的,首先随着分块的变大运行速度提高,因为缓存的利用率得到了提高,到达一定程度后,每次分块都超出了缓存一行的大小,在内循环中都需要多次访问内存,缓存的利用率反而下降,运行速度下降。
经过多次实验,得到当block size=8时取得最小值,运行速度提高了倍左右。

6.46

和上一题几乎一模一样,改改代码就完事了。
注意细节,利用对称性能够减少一些计算量。

#include 
#include 
#include 
using namespace std;

const int N = 8192;
int G[N][N];

void gen()
{
    mt19937 rng(time(0));
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            G[i][j] = rng() % 2;
}
void test(void (*fun)(int *, int, int), int block = -1)
{
    clock_t st = clock();
    fun(&G[0][0], N, block);
    cout << clock() - st << '\n';
}

void col_convert(int *G, int dim, int block)
{
    for (int i = 0; i < dim; i++)
        for (int j = 0; j < dim; j++)
            G[j * dim + i] = G[j * dim + i] | G[i * dim + j];
}

void col_convert_optimized(int *G, int dim, int block)
{
    for (int i = 0; i < dim - block + 1; i += block)
        for (int j = i; j < dim - block + 1; j += block)
            for (int m = 0; m < block; m++)
                for (int n = 0; n < block; n++)
                    G[(i + m) * dim + j + n] = G[(j + n) * dim + i + m] = G[(j + n) * dim + i + m] | G[(i + m) * dim + j + n];
    for (; i < dim; i++)
        for (j = 0; j <= i; j++)
            G[i * dim + j] = G[j * dim + i] = G[i * dim + j] | G[j * dim + i];
}

int main()
{
    gen();
    cout << "origin: ";
    test(col_convert);

    for (int block = 2; block <= 16; block++)
    {
        cout << "block size= " << block << ": ";
        test(col_convert_optimized, block);
    }

}

运行结果:

origin: 838
block size= 2: 440
block size= 3: 352
block size= 4: 316
block size= 5: 271
block size= 6: 235
block size= 7: 210
block size= 8: 177
block size= 9: 211
block size= 10: 255
block size= 11: 293
block size= 12: 339
block size= 13: 455
block size= 14: 532
block size= 15: 532
block size= 16: 527

番外

查看缓存架构。
输入命令:

cd /sys/devices/system/cpu/cpu0/cache/index0

这是cpu0的第一个缓存数据。

dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu0/cache/index0$ ls
coherency_line_size  level           physical_line_partition  shared_cpu_map  type    ways_of_associativity
id                   number_of_sets  shared_cpu_list          size            uevent

下面解释一下部分文件中的数据。

文件 注释
coherency_line_size 行/块大小
level 在第几层(Ln中的n)
number_of_sets 组数
size 缓存总大小
type 类型(Data/Instruction/Unified)
ways_of_associativity 相联数(每组行数)
shared_cpu_map 十六进制数字,代表哪几个cpu共享
shared_cpu_list 共享的cpu的列表

我的机器cpuIntel Core i5,经过查看数据,与下图层次结构一致。

缓存层次结构

接下来举个例子进行验证:

dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu1/cache/index3$ cd /sys/devices/system/cpu/cpu1/cache/index3
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu1/cache/index3$ cat level
3
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu1/cache/index3$ cat size
6144K
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu1/cache/index3$ cat type
Unified
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu1/cache/index3$ cat number_of_sets
8192
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu1/cache/index3$ cat ways_of_associativity
12
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu1/cache/index3$ cat coherency_line_size
64
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu1/cache/index3$ cat shared_cpu_list
0-3

这是cpu1中的第四个缓存,对应图中的L3统一的高速缓存(所有的核共享),我们可以看到打印出的数据表明:

  • 在第三层
  • 总大小为6.0MB
  • 统一的
  • 组数为8192
  • 相联数为12
  • 每一行大小位64B
  • 被所有的cpu共享

其他的缓存都能在这些地方中查看到数据。

再举个例子:

dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu0/cache/index0$ cd /sys/devices/system/cpu/cpu0/cache/index0
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu0/cache/index0$ cat level
1
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu0/cache/index0$ cat size
32K
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu0/cache/index0$ cat type
Data
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu0/cache/index0$ cat number_of_sets
64
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu0/cache/index0$ cat ways_of_associativity
8
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu0/cache/index0$ cat coherency_line_size
64
dyume@LAPTOP-LLU88NPC:/sys/devices/system/cpu/cpu0/cache/index0$ cat shared_cpu_list
0

这是cpu0中的第一个缓存,对应图中的L1d-cache,我们可以看到打印出的数据表明:

  • 在第一层
  • 总大小为32KB
  • 数据缓存
  • 组数为64
  • 相联数为8
  • 每一行大小位64B
  • 只被第一个处理器独享

你可能感兴趣的:(CS:APP 第6章 存储器层次结构 作业)