读csapp的第6章, 感觉cache hit rate怎么都算得和答案不一样, 越看越看不下去. 不过终于看到一点有意思的内容. 那就是根据局部性来预测不同的矩阵乘法方式的性能.
矩阵乘法, 自然不用说, 3个循环, i,j,k. 其中i表示行, j表示列, k表示中间.
任务就是比较这6个方式的性能.
首先我们先看看写最内层循环的思路. 显然是对i,j,k的全排列.
(a)(b)自然不用说, 是最经典写法, 外层循环换了顺序.
(c)(d)相似, (e)(f)相似, 而(c)与(e)思路相同, 因此仅仅需要讨论(c).
对(c), 固定了jk, 那么bjk就固定了, 变量是i, 因此可以写的是aik, 加在C[i][j]上.
显然也不存在依赖的关系,所有的读取数据都可以并发执行,写指令也一样. 因此唯一影响性能的, 就只有cache了.
然后来看每一个方法的缺失率. 假设n很大(这样的话, 不会发生读到后面, 发现很久之前的块还在的情况), block的大小是32 byte, 元素类型是double. 意味着一次可以取出4个元素. 意味着如果stride=1,也就是取完这个立即取紧挨着的, 缺失率会是0.25
(a) C不会反复取(最内层循环中), 缺失率是0, A是顺序读取, 是0.25. B不是顺序读取, 是1.
(b) 与(a)完全一样, 分析一样.
(c) B不会反复取, 缺失率是0, A不是顺序, 是1, C不是顺序, 是1.
(d) 同(c).
(e) C顺序取, 是0.25, A不会缺失, 是0. B也是顺序取, 0.25.
(f) 同(e)
从这段分析可以看出, 每一行性能相似, 而(c)是最坏的, (e)是最好的. 因此矩阵乘法最优写法是kij.
遗漏了一点, 如果只看操作次数, (a)应优于(e), 因为e最内循环读了2次内存(C,B),写了1次内存(C). 而(a)读了2次内存(A,B), 没有写内存.
但为什么(a)还是不如(e)? 其实应该是有影响的, 虽然是并发写, 但是读和写用的是同一个功能单元. 应该是被cache的差距盖过操作系数的不同.