You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
staticvoidtrans_opt_32(size_tM, size_tN, doubleA[N][M], doubleB[M][N],
doubletmp[TMPCOUNT]) {
assert(M>0);
assert(N>0);
// Traverse the matrix with a step size of 8for (size_tm=0; m<M; m+=8) {
for (size_tn=0; n<N; n+=8) {
// Divide the matrix into 8 * 8 blockfor (size_ti=m; i<m+8; i++) {
for (size_tj=n; j<n+8; j++) {
if (m==n&&i==j&&i!=n+7) {
// In the diagonal, we will not copy A[i][i] to B[i][i]// This will cause an extra eviction// We delay this copy step to the futurecontinue;
}
B[j][i] =A[i][j];
if (m==n&&j==n+7&&i!=n+7) {
// In the diagonal and j == n + 7,// means we needn't cache A[m][n] ~ A[m][n + 7] any more// So we can call the copy operation// and evict this cache line directly.B[i][i] =A[i][i];
}
}
}
}
}
assert(is_transpose(M, N, A, B));
}
The text was updated successfully, but these errors were encountered:
CMU 15213 Cache Lab
Cache Lab 是 CSAPP 这门课程的第五个 Lab。
这个 Lab 总共分为了三个部分:
从难度上来看,前面两个部分的难度相对较低,而第三个部分则比较考验脑力,有种回到了 Data Lab 解 puzzle 的感觉。
在完成这个 Lab 之前,我们还是先来回忆一下 Cache 相关的知识。
Cache Recap
Memory Hierarchy
首先我们先来看一张非常经典的图,即内存的层级结构,一般来说越底层存储量越大、越便宜、但是访问也越慢,而相应的,越顶层的内存结构存储量越小、越昂贵、访问起来也越快速。
cache(缓存,台译快取)指的是一个小而快速的存储设备,它作为存储在更大、耶更慢的设备中的数据对象的缓冲区域。因此,在存储器的体系结构中,每个 k 层其实都可以视为 k + 1 层的设备的缓存。
另外,需要注意的是,数据是以 block-sized 作为单位从内存复制到 Cache 中的。
Cache Structure
想象一个简单的场景,如果我们试图从主存中读取地址 A 对应的数据,那么我们其实遇到了两个问题。
其实这两个问题的本质是一样的 —— 怎么快速根据目标数据在主存中的地址 A 定位到缓存的地址。
对于缓存而言,机器的高速缓存被组织成为一个有 S = 2 ^ s 个高速缓存组(cache set)的数组。
每一个缓存组包含了 E 个高速缓存行 cache line。
每个 cache line 包含了
假设我们计算机系统中每个存储器地址有 m 位,那么就可以形成 M = 2 ^ m 个不同的地址。
通过将这 m 位进行拆分,我们可以很轻松的得出该地址应该对应的缓存中的位置。更具体而言,我们将 m 分割成为 m = t + s + b
General Cache Concepts
缓存中最重要的概念是 Hit 和 Miss,而其中 Miss 又被分为三种: 1) Cold Miss. 2) Capacity Miss. 3) Conflict Miss.
Hit 非常好理解,就是我们试图请求 block 4 的时候,发现 block 4 已经存在于 Cache 中了,也就是缓存命中。
而三种 Miss 则稍微有一点迷惑。
然而,最好的方式还是结合一道练习题来理解
Consider the following sequence of 10 addresses requested in the order given. We already know
在这个练习题中,我们可以得出几个结论
Lab 1 - Writing Traces for a Cache Simulator
一个相当简单的任务,要求我们自己写 Trace File,来达到要求的 hits misses 以及 eviction 次数。
这里是题目要求
以第一题为例子,要求我们发生 2 次 hits 和 1 次 eviction。
我们第一次 Load 0x00,此时的 tag = 0,set = 0,发生了一次 cold miss
我们第二次 Load 0x80,此时的 tag = 1,set = 0,因此和前一个数据指向同一个 cache set,又 E = 1,且此次数据的 tag 和前一次数据的不一样,因此会发生一次 eviction
第三次和第四次都 Load 0x80,发生两次 hits。
(剩余两题的思路都差不多,就不在这里赘述了)
Lab 2 - Writing a Cache Simulator
在这个任务中,我们需要写一个 cache simulator 来模拟 LRU cache 的运行。
没有太多好说的,只要掌握了上文关于缓存的讲解,接下来就是单纯的用 C 语言来实现。
Lab 3 - Optimizing Matrix Transpose
整个 Cache Lab 最难的一部分,也是最需要思考的一部分。在这个任务中,我们需要优化矩阵转置操作。题目会给予你缓存相关的参数(s E b),以及矩阵的尺寸。
首先我们先思考为什么需要对矩阵转置进行优化。
假设我们要将矩阵 A 转置到矩阵 B 中,按照最朴素的思路,我们的代码可能类似于
那么我们每次访问的时候,发生的场景是
在上面的思路中,如果我们访问 B[1][0],那么实际上缓存系统会将 B[1][0] ~ B[1][7] 都缓存起来,那么是否我们访问 B[1][1] 的时候就 cache hit 了呢?
答案是否定的,由于我们的缓存有大小限制,当你按照上面的代码逻辑访问到 B[1][1] 的时候,曾经缓存的 B[1][0] ~ B[1][7] 早被驱逐了,因此此时还是会存在一次新的 cache mis
所以,我们的目标是:尽量减少 cache miss。
实现的方案是:将缓存到的内容尽可能的都使用后再驱逐。(例如 B[1][0] ~ B[1][7] 我们希望只有访问 B[1][0] 的时候产生一次 miss,然后我们要将这 8 个 item 全部都使用完之后,再驱逐缓存)
更具体的代码实现就是使用分页的思想。
知道需要分页之后,可以直接写出 1024 * 1024 的优化方案
1024 * 1024 的优化还是相对简单的,因为题目给出的缓存大小也比较大。
32 * 32 的优化方案也是类似的,但是因为题目给的缓存大小较小,并且老师给的 benchmark 也比较严格,因此做了一步额外的优化。
针对对角线的元素,例如 A[0][0],如果我们直接进行 B[0][0] = A[0][0],由于 B[0][0] 和 A[0][0] 所在的 block 是一样的,就会产生额外的 eviction。当我们后后续读 A[0][1] 的时候,就造成了额外的缓存不命中。
我们的方案是,当访问 A[0][0] 的时候,我们先延迟 B[0][0] = A[0][0] 的操作。我们继续操作 B[1][0] = A[0][1], B[2][0] = A[0][2], … B[7][0] = A[0][7].
至此,A[0][0]~A[0][7] 我们只剩下 A[0][0] 没有处理了,我们此时再执行 B[0][0] = A[0][0] 的操作后,A[0][0]~A[0][7] 就全部被使用完了,因此也没有额外的缓存不命中。
The text was updated successfully, but these errors were encountered: