CS:APP3e 深入理解計算機系統_3e CacheLab實驗

詳細的題目要求和實驗資源可以到教材官網 或者 課程官網 獲取。 本次實驗難點在Part B的64 * 64部分,主要介紹這一部分。
 

Part A: 編寫緩存模擬器

前期準備:

getoptfscanf系列庫函數對於這次實驗很重要,不太明白的可以man一下,或者參考這兩篇文章:

 

注意事項:

1.由於我們的模擬器必須適應不同的s, E, b,所以數據結構必須動態申請(malloc系列),注意初始化。

2.測試數據中以“I”開頭的行是對指令緩存(i-cache)進行讀寫,我們編寫的是數據緩存(d-cache),這些行直接忽略。

3.這次實驗假設內存全部對齊,即數據不會跨越block,所以測試數據裏面的數據大小也可以忽略。

4.爲了使得評分程序正常運行,main函數最後需要加上:

<span style="color:#000000"><code>printSummary(hit_count, miss_count, eviction_count);</code></span>

5.建議把-v這個選項也實現了,這樣自己debug的時候也方便一些。另外,可以先從規模小的測試數據開始,然後用大的。

 

思路要點及其實現:

1.這次實驗只要求我們測試hit/miss/eviction的次數,並沒有實際的數據存儲 ,所以我們不用實現line中的block部分。

2.這次實驗要求使用LRU(least recently used),即沒有空模塊(valid爲0)時替換最早使用的那一個line。所以我們應該在line中實現一個能夠記錄當前line最後一次寫入的時間參量,每次”寫入“line的時候就更新一下該參量。(這一點csapp上沒有詳細說)

3.綜上,結合書上對cache的描述,我們可以得到如下數據結構:

注意到cache(sets的入口)和set(lines的入口)都是用指針實現的,sets構成一個指針數組,因爲它們不含任何數據,唯一的用處就是通過偏移量尋找到指定的line

 

下面結合代碼執行的順序對我實現的程序進行解釋,由於寫了很多註釋,就不詳細的說了(我的sublime寫不了中文,就用的英文註釋的,語法有錯還請指出)

更新:一航介紹了一個插件,可以解決Ubuntu下sublime中文輸入的問題

--> sublime-text-imfix

 

頭文件:

<span style="color:#000000"><code><span style="color:#2b91af">#include "cachelab.h"</span>
<span style="color:#2b91af">#include <stdio.h>  /* fopen freopen perror */</span>
<span style="color:#2b91af">#include <stdint.h> /* uintN_t */</span>
<span style="color:#2b91af">#include <unistd.h> /* getopt */</span>
<span style="color:#2b91af">#include <getopt.h> /* getopt -std=c99 POSIX macros defined in <features.h> prevents <unistd.h> from including <getopt.h>*/</span>
<span style="color:#2b91af">#include <stdlib.h> /* atol exit*/</span>
<span style="color:#2b91af">#include <errno.h>  /* errno */</span></code></span>

爲什麼要包含該頭文件的原因在右側註釋中寫出來了。由於我們實驗使用的64位地址,所以將tag和set的索引用64位保存就足夠了,我這裏使用了C99中的固定長度類型uintN_t,可移植性好一些。另外要注意的是,C99必須包含unistd.h和getopt.h兩個頭文件才能正常使用getopt 。

 

宏定義:

<span style="color:#000000"><code><span style="color:#2b91af">#define false 0</span>
<span style="color:#2b91af">#define true 1</span></code></span>

我喜歡用_Bool+宏定義true和false,你也可以使用stdbool.h。

 

數據結構類型定義:

<span style="color:#000000"><code><span style="color:#0000ff">typedef</span> <span style="color:#0000ff">struct</span>
{
    <span style="color:#0000ff">_Bool</span> valid;    <span style="color:#008000">/* flag whether this line/block is valid, zero at first*/</span>
    <span style="color:#0000ff">uint64_t</span> tag;   <span style="color:#008000">/* identifier to choose line/block */</span>
    <span style="color:#0000ff">uint64_t</span> time_counter;  <span style="color:#008000">/* LRU strategy counter, we should evict the block who has the min time_counter, zero at first */</span>
    <span style="color:#008000">/* We don't need to simulate the block, since we just requested to count hit/miss/eviction */</span>
}line;
<span style="color:#0000ff">typedef</span> line *entry_of_lines;
<span style="color:#0000ff">typedef</span> entry_of_lines *entry_of_sets;</code></span>

time_counter初始化的時候都是0,其值越大代表這個line最近剛剛被寫入——我們不應該替換它——所以valid爲0的line的time_counter一定也是0(最小值),因爲他們連使用都沒有被使用過,即我們一定會先替換valid爲0的line,這符合書上的策略。

<span style="color:#000000"><code><span style="color:#0000ff">typedef</span> <span style="color:#0000ff">struct</span>
{
    <span style="color:#0000ff">int</span> hit;
    <span style="color:#0000ff">int</span> miss;
    <span style="color:#0000ff">int</span> eviction;
}result;</code></span>

我將結果設計成了一個結構體,這樣函數方便返回一些。(少用全局變量)

 

main函數的數據類型:

<span style="color:#000000"><code>result Result = {0, 0, 0};
<span style="color:#0000ff">const</span> <span style="color:#0000ff">char</span> *help_message = <span style="color:#a31515">"Usage: \"Your complied program\" [-hv] -s <s> -E <E> -b <b> -t <tracefile>\n"</span> \
                     <span style="color:#a31515">"<s> <E> <b> should all above zero and below 64.\n"</span> \
                     <span style="color:#a31515">"Complied with std=c99\n"</span>;
<span style="color:#0000ff">const</span> <span style="color:#0000ff">char</span> *command_options = <span style="color:#a31515">"hvs:E:b:t:"</span>;
FILE* tracefile = <span style="color:#a31515">NULL</span>;
entry_of_sets cache = <span style="color:#a31515">NULL</span>;
<span style="color:#0000ff">_Bool</span> verbose = <span style="color:#a31515">false</span>;  <span style="color:#008000">/* flag whether switch to verbose mode, zero for default */</span>
<span style="color:#0000ff">uint64_t</span> s = 0; <span style="color:#008000">/* number of sets ndex's bits */</span>
<span style="color:#0000ff">uint64_t</span> b = 0; <span style="color:#008000">/* number of blocks index's bits */</span>
<span style="color:#0000ff">uint64_t</span> S = 0; <span style="color:#008000">/* number of sets */</span>
<span style="color:#0000ff">uint64_t</span> E = 0; <span style="color:#008000">/* number of lines */</span></code></span>

註釋已經寫的很清楚了,我解釋一下help_message的寫法,有的同學可能不知道C中字符串的寫法:兩個字符串中間只有空格,C編譯器會自動將它們合併。例如:

<span style="color:#000000"><code><span style="color:#0000ff">char</span>* test_string = <span style="color:#a31515">"hello"</span>   <span style="color:#a31515">" world"</span></code></span>

那麼test_string就會是“hello world”。

另外,在C中,一行寫不下的時候可以使用\字符隔開,編譯器會自動合併的。

 

main函數讀取參數:

<span style="color:#000000"><code>    <span style="color:#0000ff">char</span> ch;    <span style="color:#008000">/* command options */</span>

    <span style="color:#0000ff">while</span>((ch = getopt(argc, argv, command_options)) != -1)
    {
        <span style="color:#0000ff">switch</span>(ch)
        {
            <span style="color:#0000ff">case</span> <span style="color:#a31515">'h'</span>:
            {
                <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"%s"</span>, help_message);
                <span style="color:#0000ff">exit</span>(EXIT_SUCCESS);
            }

            <span style="color:#0000ff">case</span> <span style="color:#a31515">'v'</span>:
            {
                verbose = <span style="color:#a31515">true</span>;
                <span style="color:#0000ff">break</span>;
            }

            <span style="color:#0000ff">case</span> <span style="color:#a31515">'s'</span>:
            {

                <span style="color:#0000ff">if</span> (atol(optarg) <= 0)  <span style="color:#008000">/* We assume that there are at least two sets */</span>
                {
                    <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"%s"</span>, help_message);
                    <span style="color:#0000ff">exit</span>(EXIT_FAILURE);
                }
                s = atol(optarg);
                S = 1 << s;
                <span style="color:#0000ff">break</span>;
            }

            <span style="color:#0000ff">case</span> <span style="color:#a31515">'E'</span>:
            {
                <span style="color:#0000ff">if</span> (atol(optarg) <= 0)
                {
                    <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"%s"</span>, help_message);
                    <span style="color:#0000ff">exit</span>(EXIT_FAILURE);
                }
                E = atol(optarg);
                <span style="color:#0000ff">break</span>;
            }

            <span style="color:#0000ff">case</span> <span style="color:#a31515">'b'</span>:
            {
                <span style="color:#0000ff">if</span> (atol(optarg) <= 0)  <span style="color:#008000">/* We assume that there are at least two sets */</span>
                {
                    <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"%s"</span>, help_message);
                    <span style="color:#0000ff">exit</span>(EXIT_FAILURE);
                }
                b = atol(optarg);
                <span style="color:#0000ff">break</span>;
            }

            <span style="color:#0000ff">case</span> <span style="color:#a31515">'t'</span>:
            {
                <span style="color:#0000ff">if</span> ((tracefile = fopen(optarg, <span style="color:#a31515">"r"</span>)) == <span style="color:#a31515">NULL</span>)
                {
                    perror(<span style="color:#a31515">"Failed to open tracefile"</span>);
                    <span style="color:#0000ff">exit</span>(EXIT_FAILURE);
                }
                <span style="color:#0000ff">break</span>;
            }

            <span style="color:#0000ff">default</span>:
            {
                <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"%s"</span>, help_message);
                <span style="color:#0000ff">exit</span>(EXIT_FAILURE);
            }
        }
    }</code></span>

關於getopt的用法可以參考文章開頭的文章;perrorfopen的用法請man一下,fopen失敗後會設置errno的。

<span style="color:#000000"><code>    <span style="color:#0000ff">if</span> (s == 0 || b ==0 || E == 0 || tracefile == <span style="color:#a31515">NULL</span>)
    {
        <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"%s"</span>, help_message);
        <span style="color:#0000ff">exit</span>(EXIT_FAILURE);
    }</code></span>

如果讀取的參數中沒有s或者b或者E或者文件,那麼那他們將會是對應的初始值。

 

main函數調用函數並結束程序:

<span style="color:#000000"><code>    cache = InitializeCache(S, E);
    Result = ReadAndTest(tracefile, cache, S, E, s, b, verbose);
    RealseMemory(cache, S, E);   <span style="color:#008000">/* Don't forget this in C/C++, and do not double release which causes security problem */</span>
    <span style="color:#008000">//printf("hits:%d misses:%d evictions:%d\n", Result.hit, Result.miss, Result.eviction);</span>
    printSummary(Result.hit, Result.miss, Result.eviction);
    <span style="color:#0000ff">return</span> 0;</code></span>

InitializeCache是用來動態申請數據結構的,ReadAndTest是本程序的核心,用來測試hit/miss/eviction的次數。另外不要忘記或者重複釋放內存。下面分別介紹這三個函數。

 

<span style="color:#000000"><code>entry_of_sets <span style="color:#a31515">InitializeCache</span>(<span style="color:#0000ff">uint64_t</span> S, <span style="color:#0000ff">uint64_t</span> E)
{
    entry_of_sets cache;

    <span style="color:#008000">/* use calloc instead of malloc to match the default situation we designed */</span>

    <span style="color:#0000ff">if</span> ((cache = <span style="color:#0000ff">calloc</span>(S, <span style="color:#0000ff">sizeof</span>(entry_of_lines))) == <span style="color:#a31515">NULL</span>) <span style="color:#008000">/* initialize the sets */</span>
    {
        perror(<span style="color:#a31515">"Failed to calloc entry_of_sets"</span>);
        <span style="color:#0000ff">exit</span>(EXIT_FAILURE);
    }

    <span style="color:#0000ff">for</span>(<span style="color:#0000ff">int</span> i = 0; i < S; ++i)  <span style="color:#008000">/* initialize the lines in set */</span>
    {
        <span style="color:#0000ff">if</span> ((cache[i] = <span style="color:#0000ff">calloc</span>(E, <span style="color:#0000ff">sizeof</span>(line))) == <span style="color:#a31515">NULL</span>)
        {
            perror(<span style="color:#a31515">"Failed to calloc line in sets"</span>);
        }
    }

    <span style="color:#0000ff">return</span> cache;
}</code></span>

我們首先根據S(set的數目)申請一個數組,該數組元素是lines的入口的指針。接着循環S次每次申請E個line數據結構,並讓剛剛的指針數組的元素指向它們:

                                                             +-----+
                                                +-----+   +-->Valid|
                                           +---->line0+---+  +-----+
                                           |    +-----+   |
                         +---------------+ |              |  +---+
                         | set0          | |    +-----+   +-->Tag|
                      +--> entry_of_lines+------>line1|   |  +---+
                      |  +---------------+ |    +-----+   |
                      |                    |              |  +-------+
                      |  +---------------+ |    +-----+   +-->Counter|
                      |  | set1          | +---->line2|      +-------+
                      +--> entry_of_lines| |    +-----+
+--------------+      |  +---------------+ |
| cache0       +------+                    |    +-----+
| entry_of_sets|      |  +---------------+ +---->lineX|
+--------------+      |  | set2          |      +-----+
                      +--> entry_of_lines|
                      |  +---------------+
                      |
                      |  +---------------+
                      |  | setX          |
                      +--> entry_of_lines|
                         +---------------+

 

釋放之前申請的內存:

<span style="color:#000000"><code><span style="color:#0000ff">void</span> <span style="color:#a31515">RealseMemory</span>(entry_of_sets cache, <span style="color:#0000ff">uint64_t</span> S, <span style="color:#0000ff">uint64_t</span> E)
{
    <span style="color:#0000ff">for</span> (<span style="color:#0000ff">uint64_t</span> i = 0; i < S; ++i)
    {
        <span style="color:#0000ff">free</span>(cache[i]);
    }
    <span style="color:#0000ff">free</span>(cache);
}</code></span>

不解釋。

 

核心部分,測試hit/miss/eviction的次數:

<span style="color:#000000"><code>result <span style="color:#a31515">ReadAndTest</span>(FILE *tracefile, entry_of_sets cache, <span style="color:#0000ff">uint64_t</span> S, <span style="color:#0000ff">uint64_t</span> E, <span style="color:#0000ff">uint64_t</span> s, <span style="color:#0000ff">uint64_t</span> b, <span style="color:#0000ff">_Bool</span> verbose)
{
    result Result = {0, 0, 0};
    <span style="color:#0000ff">char</span> ch;
    <span style="color:#0000ff">uint64_t</span> address;
    <span style="color:#0000ff">while</span>((<span style="color:#0000ff">fscanf</span>(tracefile, <span style="color:#a31515">" %c %lx%*[^\n]"</span>, &ch, &address)) == 2)    <span style="color:#008000">/* read instruction and address from tracefile and ignore the size */</span>
                                                                        <span style="color:#008000">/* address is represented by hexadecimal, use %lx instead of %lu */</span>
    {
        <span style="color:#0000ff">if</span> (ch == <span style="color:#a31515">'I'</span>)
        {
            <span style="color:#0000ff">continue</span>; <span style="color:#008000">/* we don't care about 'I' */</span>
        }
        <span style="color:#0000ff">else</span>
        {
            <span style="color:#0000ff">uint64_t</span> set_index_mask = (1 << s) - 1;
            <span style="color:#0000ff">uint64_t</span> set_index = (address >> b) & set_index_mask;
            <span style="color:#0000ff">uint64_t</span> tag = (address >> b) >> s;
            entry_of_lines search_line = cache[set_index];


            <span style="color:#0000ff">if</span> (ch == <span style="color:#a31515">'L'</span> || ch == <span style="color:#a31515">'S'</span>) <span style="color:#008000">/* load/store can cause at most one cache miss */</span>
            {
                <span style="color:#0000ff">if</span> (verbose)    <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"%c %lx "</span>, ch, address);
                Result = HitMissEviction(search_line, Result, E, tag, verbose);
            }

            <span style="color:#0000ff">else</span> <span style="color:#0000ff">if</span> (ch == <span style="color:#a31515">'M'</span>) <span style="color:#008000">/* data modify (M) is treated as a load followed by a store to the same address.
                                   Hence, an M operation can result in two cache hits, or a miss and a hit plus an possible eviction. */</span>
            {
                <span style="color:#0000ff">if</span> (verbose)    <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"%c %lx "</span>, ch, address);
                Result = HitMissEviction(search_line, Result, E, tag, verbose);  <span style="color:#008000">/* load, hit/miss(+eviction) */</span>
                Result = HitMissEviction(search_line, Result, E, tag, verbose);  <span style="color:#008000">/* store, must hit */</span>
            }

            <span style="color:#0000ff">else</span>    <span style="color:#008000">/* ignore other cases */</span>
            {
                <span style="color:#0000ff">continue</span>;
            }
        }
    }
    <span style="color:#0000ff">return</span> Result;
}</code></span>

如果命令是“L”或者“M”,我們就進入HitMissEviction一次判斷其是否hit或者miss以及是否發生替換,如果是M就相當於一次“L”和一次“M”,需要進入HitMissEviction兩次,其結果可能爲兩次hit,也可能爲一次miss+(eviction)一次hit。我們在ReadAndTest裏通過一些位運算找到對應的set(即entry_of_lines),然後以此作爲參數調用HitMissEviction 判斷到底是miss(有沒有eviction)還是hit。

<span style="color:#000000"><code>result <span style="color:#a31515">HitMissEviction</span>(entry_of_lines search_line, result Result, <span style="color:#0000ff">uint64_t</span> E, <span style="color:#0000ff">uint64_t</span> tag, <span style="color:#0000ff">_Bool</span> verbose)
{
    <span style="color:#0000ff">uint64_t</span> oldest_time = UINT64_MAX;
    <span style="color:#0000ff">uint64_t</span> youngest_time = 0;
    <span style="color:#0000ff">uint64_t</span> oldest_block = UINT64_MAX;
    <span style="color:#0000ff">_Bool</span> hit_flag = <span style="color:#a31515">false</span>;

    <span style="color:#0000ff">for</span> (<span style="color:#0000ff">uint64_t</span> i = 0; i < E; ++ i)
    {
        <span style="color:#0000ff">if</span> (search_line[i].tag == tag && search_line[i].valid) <span style="color:#008000">/* hit */</span>
        {
            <span style="color:#0000ff">if</span> (verbose)    <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"hit\n"</span>);
            hit_flag = <span style="color:#a31515">true</span>;
            ++Result.hit;
            ++search_line[i].time_counter; <span style="color:#008000">/* update the time counter */</span>
            <span style="color:#0000ff">break</span>;
        }
    }

    <span style="color:#0000ff">if</span> (!hit_flag)  <span style="color:#008000">/* miss */</span>
    {
        <span style="color:#0000ff">if</span> (verbose)    <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"miss"</span>);
        ++Result.miss;
        <span style="color:#0000ff">uint64_t</span> i;
        <span style="color:#0000ff">for</span> (i = 0; i < E; ++i)    <span style="color:#008000">/* search for the oldest modified block (invalid blocks are oldest as we designed) */</span>
        {
            <span style="color:#0000ff">if</span> (search_line[i].time_counter < oldest_time)
            {
                oldest_time = search_line[i].time_counter;
                oldest_block = i;
            }
            <span style="color:#0000ff">if</span> (search_line[i].time_counter > youngest_time)    <span style="color:#008000">/* search for the youngest modified block to update the new block's time counter */</span>
            {
                youngest_time = search_line[i].time_counter;
            }
        }

        search_line[oldest_block].time_counter = youngest_time + 1;
        search_line[oldest_block].tag = tag;

        <span style="color:#0000ff">if</span> (search_line[oldest_block].valid) <span style="color:#008000">/* It's a valid block, ++eviction */</span>
        {
            <span style="color:#0000ff">if</span> (verbose)    <span style="color:#0000ff">printf</span>(<span style="color:#a31515">" and eviction\n"</span>);
            ++Result.eviction;
        }
        <span style="color:#0000ff">else</span>
        {
            <span style="color:#0000ff">if</span> (verbose)    <span style="color:#0000ff">printf</span>(<span style="color:#a31515">"\n"</span>);
            search_line[oldest_block].valid = <span style="color:#a31515">true</span>;
        }
    }
    <span style="color:#0000ff">return</span> Result;
}
</code></span>

HitMissEviction裏面需要注意的地方是時間參量的更新,我們既要找到最“老”的line,也要同時記住最“新”的line的時間參量(我這裏是遍歷搜索,也可以在設計set的數據類型時設計爲結構體,其中放一個最新的時間參量),以此來更新時間參量。如果我們要替換的line的valid爲1,則發生了一次eviction。

 

partA完整代碼下載

 

運行結果:

 


 

Part B: 優化矩陣轉置

前期準備:

最簡單的轉置實現:

<span style="color:#000000"><code><span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> i = 0; i < N; ++i)
{
    <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> j = 0; j < M; ++j)
    {
        dst[j][i] = src[i][j]
    }
}</code></span>

 

注意事項:

1.最多隻能定義12個局部變量。

2.不允許使用位運算,不允許使用數組或者malloc。

3.不能改變原數組A,但是可以修改轉置數組B

 

思路要點及其實現:

1.block的大小爲32byte,即可以放下8個int,即miss的最低限度是1/8

2.cache的大小爲32*32,即32個block,128個int。

3.blocking是一種很好的優化技術,這次實驗基本就靠他了;)其大致概念爲以數據塊的形式讀取數據,完全利用後丟棄,然後讀取下一個,這樣防止block利用的不全面。可以參考卡耐基梅隆的一篇文章:waside-blocking

4.儘量將一個block讀入完全或者寫入完全,例如假設一個block可以放兩個數,進行如下轉置操作,其讀取時“盡力”讀取,完全利用了一個block,但是在寫入的時候浪費了1/2的空間。

5.儘量使用剛剛使用的block(還是“熱乎的”),因爲它們很可能還沒有被替換,hit的概率會很大。

6.讀出和寫入的時候注意判斷這兩個位置映射在cache中的位置是否相同,(我們這個cache是直接映射,一個set只有一個block,所以絕大部分的miss伴隨着替換),也可以說,我們要儘量避免替換的發生。

 

下面我結合實驗要求的三個例子具體講。

 

32 × 32 (M = 32, N = 32)

由於我們的block能存8個int,所以blocking的數據塊最好是以它爲單位的,這樣能儘可能利用block,例如8 * 8或者16 * 16。

在32*32的情況中,一行是32個int,也就是4個block,所以cache可以存8行,由此可以推出映射衝突的情況:只要兩個int之間相差8行的整數倍,那麼讀取這兩個元素所在的block就會發生替換,再讀後面連續的元素也會不斷髮生替換(thrashing,csapp中文版上面翻譯的是“抖動”,感覺一點也不形象。。。)下圖中標出了與一個元素衝突的位置(包括他自己本身的位置,因爲我們A,B兩個數組在內存中是相鄰的,而32*32又是cache的整數倍。):

但是轉置的過程中這樣的情況會發生嗎?圖中的BCD三點對於A來說僅僅是行差了8K,這在轉置中是不可能發生的!因爲轉置是將A[i][j]送到B[j][i],不會有B[i][j+8k]的情況出現。

但是對於A點而言,如果A[i][j]中i = j,那麼B也會是B[i][j],即映射遇到同一個block中,而當i = j的時候,就是對角線的情況:

所以現在我們只要單獨處理對角線的情況就可以啦,這裏有兩種處理方法:

  1. 由於我們可以使用12個局部變量,所以我們可以用8個局部變量一次性將包含對角線int的block全部讀出,這樣即使寫入的時候替換了之前的block也不要緊,因爲我們已經全部讀出了。
  2. 我們用一個局部變量暫時先保存這個對角線元素,並用另一個變量記錄它的位置,待block的其他7個元素寫完以後,我們再將這個會引起替換的元素寫到目的地。

下面的代碼使用第一種方法,另外,由於相差8行就會有衝突,所以我們blocking的時候用8*8的數據塊。

<span style="color:#000000"><code><span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> i = 0; i < N; i += 8)
{
    <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> j = 0; j < M; j += 8)
    {
        <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> k = i; k < i + 8; ++k)
        {
            <span style="color:#0000ff">int</span> temp_value0 = A[k][j];
            <span style="color:#0000ff">int</span> temp_value1 = A[k][j+1];
            <span style="color:#0000ff">int</span> temp_value2 = A[k][j+2];
            <span style="color:#0000ff">int</span> temp_value3 = A[k][j+3];
            <span style="color:#0000ff">int</span> temp_value4 = A[k][j+4];
            <span style="color:#0000ff">int</span> temp_value5 = A[k][j+5];
            <span style="color:#0000ff">int</span> temp_value6 = A[k][j+6];
            <span style="color:#0000ff">int</span> temp_value7 = A[k][j+7];
          
            B[j][k] = temp_value0;
            B[j+1][k] = temp_value1;
            B[j+2][k] = temp_value2;
            B[j+3][k] = temp_value3;
            B[j+4][k] = temp_value4;
            B[j+5][k] = temp_value5;
            B[j+6][k] = temp_value6;
            B[j+7][k] = temp_value7;
        }
    }
}</code></span>

運行結果:

 

64 × 64 (M = 64, N = 64)

此時,數組一行有64個int,即8個block,所以每四行就會填滿一個cache,即兩個元素相差四行就會發生衝突。

如果我們使用4*4的blocking,這樣固然可以成功,但是每次都會有1/2的損失,優化不夠。如果使用剛剛的8*8的blocking,那麼在寫入的時候就會發生衝突:

這個時候可以使用一下“divide and conquer”的思想,我們先將8*8的塊分成四部分:

本來我們是要將右上角的2移動到左下角的3的(轉置),但是爲了防止衝突我們先把他們移動到2的位置,以後再來處理:

對於3和4,我們採取一樣的策略,就可以得到如下結果,在這個過程中沒有抖動的發生

這個時候再將23互換就可以啦。

但是,測試以後並不能滿足優化的要求,說明我們將23轉換的時候(或是之後)又發生很多miss,所以我們應該在將右上角的34轉換的過程中將2的位置復原,這裏的復原是整個實驗中最具技巧性的,由前面的要點5:儘量使用剛剛使用的block(還是“熱乎的”),因爲它們很可能還沒有被替換,hit的概率會很大。我們在轉換2的時候逆序轉換:

同時在讀取右上角34的時候按列來讀,這樣的好處就是把2換到3的過程中是從下到上按行換的,因爲這樣可以先使用“最熱乎”的block:

接着轉換:

最後的效果:

<span style="color:#000000"><code><span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> i = 0; i < N; i += 8)
{
    <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> j = 0; j < M; j += 8)
    {
        <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> k = i; k < i + 4; ++k)
        {
        <span style="color:#008000">/* 讀取1 2,暫時放在左下角1 2 */</span>
            <span style="color:#0000ff">int</span> temp_value0 = A[k][j];
            <span style="color:#0000ff">int</span> temp_value1 = A[k][j+1];
            <span style="color:#0000ff">int</span> temp_value2 = A[k][j+2];
            <span style="color:#0000ff">int</span> temp_value3 = A[k][j+3];
            <span style="color:#0000ff">int</span> temp_value4 = A[k][j+4];
            <span style="color:#0000ff">int</span> temp_value5 = A[k][j+5];
            <span style="color:#0000ff">int</span> temp_value6 = A[k][j+6];
            <span style="color:#0000ff">int</span> temp_value7 = A[k][j+7];
          
            B[j][k] = temp_value0;
            B[j+1][k] = temp_value1;
            B[j+2][k] = temp_value2;
            B[j+3][k] = temp_value3;
          <span style="color:#008000">/* 逆序放置 */</span>
            B[j][k+4] = temp_value7;
            B[j+1][k+4] = temp_value6;
            B[j+2][k+4] = temp_value5;
            B[j+3][k+4] = temp_value4;
        }
         <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> l = 0; l < 4; ++l)
        {
           <span style="color:#008000">/* 按列讀取 */</span>
            <span style="color:#0000ff">int</span> temp_value0 = A[i+4][j+3-l];
            <span style="color:#0000ff">int</span> temp_value1 = A[i+5][j+3-l];
            <span style="color:#0000ff">int</span> temp_value2 = A[i+6][j+3-l];
            <span style="color:#0000ff">int</span> temp_value3 = A[i+7][j+3-l];
            <span style="color:#0000ff">int</span> temp_value4 = A[i+4][j+4+l];
            <span style="color:#0000ff">int</span> temp_value5 = A[i+5][j+4+l];
            <span style="color:#0000ff">int</span> temp_value6 = A[i+6][j+4+l];
            <span style="color:#0000ff">int</span> temp_value7 = A[i+7][j+4+l];
           
           <span style="color:#008000">/* 從下向上按行轉換2到3 */</span>
            B[j+4+l][i] = B[j+3-l][i+4];
            B[j+4+l][i+1] = B[j+3-l][i+5];
            B[j+4+l][i+2] = B[j+3-l][i+6];
            B[j+4+l][i+3] = B[j+3-l][i+7];
           <span style="color:#008000">/* 將3 4放到正確的位置 */</span>
            B[j+3-l][i+4] = temp_value0;
            B[j+3-l][i+5] = temp_value1;
            B[j+3-l][i+6] = temp_value2;
            B[j+3-l][i+7] = temp_value3;
            B[j+4+l][i+4] = temp_value4;
            B[j+4+l][i+5] = temp_value5;
            B[j+4+l][i+6] = temp_value6;
            B[j+4+l][i+7] = temp_value7;
        } 
    }
}</code></span>

運行結果:

 

61 × 67 (M = 61, N = 67)

這個題只要求miss < 2000,比較寬鬆。

這個時候由於不對稱,所以也不存在相差4行就必定衝突的情況,我們可以試一下16 * 16這種blocking。但是“對角線”的元素(橫座標等於縱座標)肯定還是會衝突的(其實這個時候不是對角線了,因爲不是正方形)。我們在這裏用32*32分析中的第二種方法。

<span style="color:#000000"><code><span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> i = 0; i < N; i += 16)
{
    <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> j = 0; j < M; j += 16)
    {
        <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> k = i; k < i + 16 && k < N; ++k)
        {
            <span style="color:#0000ff">int</span> temp_position = -1;
            <span style="color:#0000ff">int</span> temp_value = 0;
            <span style="color:#0000ff">int</span> l;
            <span style="color:#0000ff">for</span> (l = j; l < j + 16 && l < M; ++l)
            {
                <span style="color:#0000ff">if</span> (l == k) <span style="color:#008000">/* 橫座標等於縱座標,局部變量暫存,整個block讀完再處理 */</span>
                {
                    temp_position = k;
                    temp_value = A[k][k];
                }
                <span style="color:#0000ff">else</span>
                {
                    B[l][k] = A[k][l];
                }
            }
            <span style="color:#0000ff">if</span> (temp_position != -1) <span style="color:#008000">/* 遇到了衝突元素 */</span> 
            {
                B[temp_position][temp_position] = temp_value;
            }
        }
    }
}</code></span>



運行結果:

 


 

partB完整代碼下載

 


 

最終結果爲滿分:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章