深入理解計算機系統_3e 第六章家庭作業 CS:APP3e chapter 6 homework


6.22

假設磁道沿半徑均勻分布,即總磁道數和(1-x)r成正比,設磁道數為(1-x)rk;

由題單個磁道的位數和周長成正比,即和半徑xr成正比,設單個磁道的位數為xrz;

其中r、k、z均為常數。

所以C = (1-x)rk * xrz = (-x^2 + x) * r^2 * kz,即需要-x^2 + x最大,得到x = 0.5。


6.23

seek time : 4 ms

average rotational latency : 0.5 * 60 / 15000 * 1000 = 2 ms

transfer time : 60 / 15000 / 800 * 1000= 0.005 ms

4 + 2 + 0.005 = 6.005 ms


6.24

A.

2MB = 512 bytes * 4096,即需要讀取4096個扇區。

定位時間為:4 + 0.5*60/15000*1000 = 6 ms

最理想的情況下,這4096個扇區都在一個柱面上(一個磁道讀完后繼續讀下一個,磁頭不用移動),也就是4096/1000 = 5個磁道。

transfer time = 4096 / 1000 * 60 / 15000 * 1000 = 16.384 ms

所以理想時間為:6 + 16.384 = 22.384 ms

B.

2MB = 512 bytes * 4096,即需要讀取4096個扇區。

定位時間為:4 + 0.5*60/15000*1000 = 6 ms

在完全隨機的情況下,這4096個扇區分布在不同的磁道上,每一個扇區讀完以后磁頭都要再次去定位。

所以總的時間為:6 * 4096 + transfer time = 24592.384 ms (這里書上有一個類似的題目,但是沒有加上transfer time ,我覺得還是要加上)


6.25

    S    t     s    b
1. 64    24    6    2
2. 1     30    0    2
3. 128   22    7    3
4. 1     29    0    3
5. 32    22    5    5
6. 8     24    3    5

6.26

    m    C    B    E    S    t    s    b
1.      2048           256
2.            4    4
3.                           25   6
4.            32                       5

6.27

A.

0x08A4 0x08A5 0x08A6 0x08A7

0x0704 0x0705 0x0706 0x0707

B.

0x1238 0x1239 0x123A 0x123B


6.28

A.

None

B.

0x18F0 0x18F1 0x18F2 0x18F3

0x00B0 0x00B1 0x00B2 0x00B3

C.

0x0E34 0x0E35 0x0E36 0x0E37

D.

0x1BDC 0x1BDD 0x1BDE 0x1BDF


6.29

A.

CTCTCTCTCTCTCTCT, CICI, COCO

B.

Hit?    Read value(or unknown)
N           -
Y           unknown
Y           0xC0

6.30

A.

C = S * E * B = 128 Bytes

B.

CTCTCTCTCTCTCTCT, CICICI, COCO


6.31

A.

00111000, 110, 10

B.

CO 0x2

CI 0x6

CT 0x38

Cache hit? Y
Cache hit? N 2020-6-10

Cache byte returned 0xEB


6.32

A.

10110111, 010, 00

B.

CO 0x0

CI 0x2

CT 0xB7

Cache hit? N

Cache byte returned -


6.33

0x1788 0x1789 0x178A 0x178B

0x16C8 0x16C9 0x16CA 0x16CB


6.34

cache共有兩個block,分別位於兩個set中,設他們為b1和b2。每個block可以放下4個int類型的變量,也就是數組中的一行。在這一題中,源數組和目的數組是相鄰排列的。所以內存和cache的映射情況是這樣的:

b1 : src[0][] src[2][] dst[0][] dst[2][]

b2 : src[1][] src[3][] dst[1][] dst[3][]

              dst array                                   src array
       Col.0  Col.1  Col.2  Col.3                 Col.0  Col.1  Col.2  Col.3
Row0     m      m       h     m                    m       m      m      m
Row1     m      h       m     h                    m       m      m      m
Row2     m      m       h     m                    m       m      m      m
Row3     m      h       m     h                    m       m      m      m

6.35

cache共有八個block,分別位於八個set中,設他們為b1、b2、b3、b4、b5、b6、b7、b8。每個block可以放下4個int類型的變量,也就是數組中的一行。在這一題中,源數組和目的數組是相鄰排列的。所以內存和cache的映射情況是這樣的:(這時不會有沖突)

b1 : src[0][]

b2 : src[1][]

b3 : src[2][]

b4 : src[3][]

b5 : dst[0][]

b6 : dst[1][]

b7 : dst[2][]

b8 : dst[3][]

              dst array                                   src array
       Col.0  Col.1  Col.2  Col.3                 Col.0  Col.1  Col.2  Col.3
Row0     m      h      h      h                     m      h      h      h
Row1     m      h      h      h                     m      h      h      h
Row2     m      h      h      h                     m      h      h      h
Row3     m      h      h      h                     m      h      h      h

6.36

A.

cache共有32個block,分別位於32個set中,每個block可以放下4個int類型的變量,所以所有的block可以放下x數組中的一行。由映射關系,x[0][i]和x[1][i]對應的set是一樣的。所以每一次的運算都會發生miss的情況,所以miss rate = 100%。

B.

cache共有64個block,分別位於64個set中,每個block可以放下4個int類型的變量,所以所有的block可以放下x數組中的兩行,即全部放入。每四次讀取中的第一次會發生miss,所以miss rate = 25%。

C.

cache共有32個block,分別位於16個set中,每個block可以放下4個int類型的變量,每個set可以放下8個int類型的變量,所有的block可以放下x數組中的一行。由映射關系,x[0][i]和x[1][i]對應的set是一樣的,x[y][i]和x[y][i+64]對應的set也是一樣的。

對於x[0][0] * x[1][0] ~ x[0][63] * x[1][63] ,每四次運算會有第一次miss。

對於x[0][64] * x[1][64] ~ x[0][127] * x[1][127] ,每四次運算會有第一次miss(擦去前面warm up的cache)。

綜上,miss rate = 25%。

D.

不會,因為此時block大小是限制因素(每四次讀取第一次miss)。

E.

會,更大的block會降低miss rate,因為miss只發生在第一次讀入block的時候,所以更大的block會使得miss占總讀取的比例降低。


6.37

cache共有256個block,分別位於256個set中,每個block可以放下4個int類型的變量,所有的block可以放下1024個int類型的變量。

當N = 64:

映射關系:a[0][0] ~ a[15][63]、a[16][0] ~ a[31][63]、a[32][0] ~ a[47][63]、a[48][0] ~ a[63][63] 互相重疊。

sumA按照行來讀取,所以每四次讀取第一次都會miss,即miss rate = 25%。

sumB按照列來讀取,所以每一次讀取都會發生miss(讀取后的block又會被覆蓋),即miss rate = 100%。

sumC按照列來讀取,但是每次讀取后都會按照行再讀取一次,所以每四次讀取會有兩次miss,即miss rate = 50%。

當N = 60

映射關系:a[0][0] ~ a[17][3]、a[17][4] ~ a[34][7]、a[34][8] ~ a[51][11]、a[51][12] ~ a[59][59]互相重疊,其中最后的a[51][12] ~ a[59][59]沒有到達cache的尾部。

sumA按照行來讀取,所以每四次讀取第一次都會miss,即miss rate = 25%。

sumB按照列來讀取,這里的情況有些復雜,我寫了一個程序來分析:

#include <stdio.h>

#define SIZEOFCACHE 256
#define SIZEOFBLOCK 4
#define N 60


int main()
{
    int cache[SIZEOFCACHE];
    for (int k = 0; k < SIZEOFCACHE; ++k)
    {
    	cache[k] = -1;
    }

    int read = 0;
    int miss = 0;
    for (int j = 0; j < N; ++j)
    {
    	for (int i = 0; i < N; ++i)
    	{
    		//read a[i][j]
    		++read;
    		int position = i * N + j;
    		int need_start = position/SIZEOFBLOCK;
    		if (cache[need_start%SIZEOFCACHE] != need_start)
    		{
    			++miss;
    			cache[need_start%SIZEOFCACHE] = need_start;
    		}
    	}
    }

    printf("%g\n", miss/(double)read);

    return 0;
}

輸出結果為25%。

C.

將上面程序的循環部分更改為:

    for (int j = 0; j < N; j+=2)
    {
    	for (int i = 0; i < N; i+=2)
    	{
    		//read a[i][j] a[i+1][j] a[i][j+1] a[i+1][j+1]
    		++read;
    		int position = i * N + j;
    		int need_start = position/SIZEOFBLOCK;
    		if (cache[need_start%SIZEOFCACHE] != need_start)
    		{
    			++miss;
    			cache[need_start%SIZEOFCACHE] = need_start;
    		}

    		++read;
    		position = (i+1) * N + j;
    		need_start = position/SIZEOFBLOCK;
    		if (cache[need_start%SIZEOFCACHE] != need_start)
    		{
    			++miss;
    			cache[need_start%SIZEOFCACHE] = need_start;
    		}

    		++read;
    		position = i * N + j + 1;
    		need_start = position/SIZEOFBLOCK;
    		if (cache[need_start%SIZEOFCACHE] != need_start)
    		{
    			++miss;
    			cache[need_start%SIZEOFCACHE] = need_start;
    		}

    		++read;
    		position = (i+1) * N + j + 1;
    		need_start = position/SIZEOFBLOCK;
    		if (cache[need_start%SIZEOFCACHE] != need_start)
    		{
    			++miss;
    			cache[need_start%SIZEOFCACHE] = need_start;
    		}
    	}
    }

輸出結果為25%。


6.38

這個cache有64個block,每個block可以放4個int類型的變量,也就是一個point_color的結構體,即cache總共可以放置64個結構體。

映射關系為:square[0][0] ~ square[3][15]、square[4][0] ~ square[7][15]、square[8][0] ~ square[11][15]、square[12][0] ~ square[15][15] 互相重疊。

A.

16 * 16 * 4 = 1024

B.

這個程序是按照行來寫的,所以每四次寫入只有第一次miss,即miss的次數為1024 / 4 = 256

C.

25%


6.39

這個cache有64個block,每個block可以放4個int類型的變量,也就是一個point_color的結構體,即cache總共可以放置64個結構體。

映射關系為:square[0][0] ~ square[3][15]、square[4][0] ~ square[7][15]、square[8][0] ~ square[11][15]、square[12][0] ~ square[15][15] 互相重疊。

A.

16 * 16 * 4 = 1024

B.

這個程序是按照列來寫的,每四次寫入只有第一次miss(每次都完整利用了一個block,沒有讀入block的浪費,此時miss rate只取決於block的大小),即miss的次數為1024 / 4 = 256

C.

25%


6.40

這個cache有64個block,每個block可以放4個int類型的變量,也就是一個point_color的結構體,即cache總共可以放置64個結構體。

映射關系為:square[0][0] ~ square[3][15]、square[4][0] ~ square[7][15]、square[8][0] ~ square[11][15]、square[12][0] ~ square[15][15] 互相重疊。

A.

16 * 16 + 3 * 16 * 16 = 1024

B.

對於第一個循環,每一次寫入都會發生miss的情況,最后cache中保存的是square[12][0] ~ square[15][15],而第二個循環又從頭開始寫入,所以每三次寫入的第一次都會發生miss。總的miss次數就是16 * 16 * 2 = 512。

C.

50%


6.41

這個cache有16K個block,每個block可以放4個char類型的變量,也就是一個pixel的結構體,即cache總共可以放置16K個結構體。buffer里面一共有480 * 640 = 300K個結構體,所以映射時會有18個完全重疊的,最后一次重疊3/4.

這個程序按照列來寫,每四次寫入只有第一次miss(每次都完整利用了一個block,沒有讀入block的浪費,此時miss rate只取決於block的大小),所以miss rate = 25%。


6.42

這個cache有16K個block,每個block可以放4個char類型的變量,也就是一個pixel的結構體,即cache總共可以放置16K個結構體。buffer里面一共有480 * 640 = 300K個結構體,所以映射時會有18個完全重疊的,最后一次重疊3/4.

這個程序實際上就是按照行來寫的指針版本,每四次寫入只有第一次miss,miss rate = 25%。


6.43

這個cache有16K個block,每個block可以放4個char類型的變量,也就是一個pixel的結構體,即cache總共可以放置16K個結構體。buffer里面一共有480 * 640 = 300K個結構體,所以映射時會有18個完全重疊的,最后一次重疊3/4.

這個程序實際上還是按照行來寫的指針版本,但是只寫了buffer數組的1/4。每四次寫入只有第一次miss,miss rate = 25%。


6.44

為了辨識緩存的大小,選取中間的列(例如S8)來判斷——避免CPU的prefetching帶來干擾。可以看出,在32K和512K以及8M的地方有明顯的落差,所以判斷L1:32k、L2:512k、L3:8M。

lscpu驗證分析正確:


6.45

這個題要求我們利用第5章和第6章中學到的優化知識。對於第5章,就是減少循環的數據依賴,從而利用流水線並行執行;對於第6章,則是從兩個方面(temporary、spatial)利用數據的“本地性”。

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

以上的關鍵語句中的乘法和加法已經實現了循環之間獨立,src也是按照行讀入的,但是dst卻是按照列讀入的,這樣沒有充分利用每一次讀入的block。於是我們想到可不可以每一次讀入dst[j*dim + i]所在的block之后繼續寫入例如dst[j*dim + i + 1] dst[j*dim + i + 2]這樣的變量,但是這樣有需要src的部分變為src[(i+1)*dim +j]等等,所以我們現在不僅要“橫向”擴展dst,還要“縱向”擴展src,其實這是一種叫做blocking的技術,即每次讀入一塊數據,對此塊數據完全利用后拋棄,然后讀取下一個塊。可以參考csapp網上給的注解:MEM:BLOCKING — Using blocking to increase temporal locality

設我們的數據塊的寬度是B,由於我們要對兩個數組進行讀寫操作,所以2B^2 < C(其中C是cache的容量),在此限制下B盡可能取大。

#define B chunkdatas_length_of_side
void faster_transpose(int *dst, int *src, int dim)
{
	long limit = dim * dim;
	for (int i = 0; i < dim; i += B)
	{
		for (int j = 0; j < dim; j += B)
		{
			/* Using blocking to improve temporal locality */

			for (int k = i; k < i+B; ++k)
			{
				for (int l = j; l < j+B; ++l)
				{
                    /* independent calculations */
					int d = l*dim + k;
					int s = k*dim + l;
					if (s < limit && d < limit)
					{
						dst[d] = src[s]
					}
				}
			}

		}
	}
}

6.46

這個題僅僅是6.45的一個實踐版。但這個題有一個小技巧,就是G[d] || G[s]這個運算對於G[d]和G[s]都是一樣的,所以只用計算一次,從而我們只用計算對角線的上半部分的內容,也就是第二外層循環的j不用從0而是從最外層循環的i開始。

#define B chunkdatas_length_of_side
void faster_col_convert(int *G, int dim)
{
	long limit = dim * dim;
	for (int i = 0; i < dim; i += B)
	{
		for (int j = i; j < dim; j += B)
		{
			/* Using blocking to improve temporal locality */

			for (int k = i; k < i+B; ++k)
			{
				for (int l = j; l < j+B; ++l)
				{
                    /* independent calculations */
					int d = l*dim + k;
					int s = k*dim + l;
					if (s < limit && d < limit)
					{
                        _Bool temp = G[d] || G[s];
						G[d] = temp;
						G[s] = temp;
					}
				}
			}

		}
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM