如何解决通过增加数组的大小来减少缓存未命中 - 为什么这样做有效?
鉴于教科书中的这段代码Randal E. Bryant、David R. O'Hallaron - 计算机系统。程序员的观点 [第 3 版](2016 年,Pearson):
float dotprod(float x[8],float y[8])
{
float sum = 0.0;
int i;
for (i = 0; i < 8; i++)
sum += x[i] * y[i];
return sum;
}
这是缓存信息:
sizeof(float) = 4
。
数组 x 从内存地址 0x0 开始并按行优先顺序存储。 y 跟随 x。
在下面的每种情况下,缓存最初都是空的。
唯一的内存访问是对数组 x 和 y 的条目。所有其他变量都存储在寄存器中。
缓存大小为 32 字节,缓存块大小为 16 字节。缓存是直接映射缓存。
我们得到的解释是,由于不断的颠簸,未命中率为 100%。然后他们继续说:
我想知道为什么 float x[12]
是他们的选择,而不是这些选项之一,例如:
x[9]
x[10]
x[11]
x[16]
比如为什么这 4 个选项都不起作用(有人告诉我它不起作用?但我不确定为什么,因为该人没有给出解释)并且只有 x[12]
会起作用作为 x[8]
数组的替代品。或者他们是否对代码中存在的缓存未命中百分比给出了不同的变化?
解决方法
在具有 16 字节块的 32 字节直接映射缓存中,您将有 2 个块。这意味着对于给定的地址,您将有 1 位用于标识块索引,4 位用于用于标识块内特定字节的块偏移(2 位用于字偏移,2 位用于字节偏移),其余的将是标签。
缓存看起来像这样:
Valid? | Tag | Index | Data
-------+-----+-------+-----
| | 0 | (16 bytes)
-------+-----+-------+-----
| | 1 | (16 bytes)
-------+-----+-------+-----
为了简单起见,让我们假设 8 位地址(地址越长,你只会得到一个更长的标签,其他没有变化)。在 8 位地址的情况下,您将有 3 位用于标记,1 位用于块索引,4 位用于块偏移。因此,给定一个地址,例如0x34
,你可以这样分解:
block offset
tag |
001|1|0100
|
block index
现在假设两个数组一个接一个在内存中,就像这样:
x[0] | x[1] | ... | x[7] | y[0] | y[1] | ... | y[7]
如果 x
从地址 0x0 开始,我们会遇到以下情况:
element address element address
x[0] 000|0|0000 (0) y[0] 001|0|0000 (32)
x[1] 000|0|0100 (4) y[1] 001|0|0100 (36)
x[2] 000|0|1000 (8) y[2] 001|0|1000 (40)
x[3] 000|0|1100 (12) y[3] 001|0|1100 (44)
x[4] 000|1|0000 (16) y[4] 001|1|0000 (48)
x[5] 000|1|0100 (20) y[5] 001|1|0100 (52)
x[6] 000|1|1000 (24) y[6] 001|1|1000 (56)
x[7] 000|1|1100 (28) y[7] 001|1|1100 (60)
| |
block index block index
如您所见,这里的问题是在具有相同数组索引的 x
和 y
的任意两个元素之间,块索引始终相同。
现在假设您在 x
结束后添加适当的填充:
x[0] | x[1] | ... | x[7] | ...PADDING... | y[0] | y[1] | ... | y[7]
你现在的情况好多了:
element addr element addr
x[0] 000|0|0000 (0) y[0] 01|1|0000 (48)
x[1] 000|0|0100 (4) y[1] 01|1|0100 (52)
x[2] 000|0|1000 (8) y[2] 01|1|1000 (56)
x[3] 000|0|1100 (12) y[3] 01|1|1100 (60)
x[4] 000|1|0000 (16) y[4] 10|0|0000 (64)
x[5] 000|1|0100 (20) y[5] 10|0|0100 (68)
x[6] 000|1|1000 (24) y[6] 10|0|1000 (72)
x[7] 000|1|1100 (28) y[7] 10|0|1100 (76)
| |
block index block index
确实,这种情况是最佳的,您只会有 4 次缓存未命中(x[0]
、y[0]
、x[4]
、y[4]
)。
为什么其他选项不起作用?好吧,让我们看看。
=== WITH x[9] =======================================
element address element address
x[0] 000|0|0000 (0) y[0] 001|0|0100 (36)
x[1] 000|0|0100 (4) y[1] 001|0|1000 (40)
x[2] 000|0|1000 (8) y[2] 001|0|1100 (44)
x[3] 000|0|1100 (12) y[3] 001|1|0000 (48)
x[4] 000|1|0000 (16) y[4] 001|1|0100 (52)
x[5] 000|1|0100 (20) y[5] 001|1|1000 (56)
x[6] 000|1|1000 (24) y[6] 001|1|1100 (60)
x[7] 000|1|1100 (28) y[7] 010|0|0000 (64)
=== WITH x[10] ======================================
element address element address
x[0] 000|0|0000 (0) y[0] 001|0|1000 (40)
x[1] 000|0|0100 (4) y[1] 001|0|1100 (44)
x[2] 000|0|1000 (8) y[2] 001|1|0000 (48)
x[3] 000|0|1100 (12) y[3] 001|1|0100 (52)
x[4] 000|1|0000 (16) y[4] 001|1|1000 (56)
x[5] 000|1|0100 (20) y[5] 001|1|1100 (60)
x[6] 000|1|1000 (24) y[6] 010|0|0000 (64)
x[7] 000|1|1100 (28) y[7] 010|0|0100 (68)
=== WITH x[11] =====================================
element address element address
x[0] 000|0|0000 (0) y[0] 001|0|1100 (44)
x[1] 000|0|0100 (4) y[1] 001|1|0000 (48)
x[2] 000|0|1000 (8) y[2] 001|1|0100 (52)
x[3] 000|0|1100 (12) y[3] 001|1|1000 (56)
x[4] 000|1|0000 (16) y[4] 001|1|1100 (60)
x[5] 000|1|0100 (20) y[5] 010|0|0000 (64)
x[6] 000|1|1000 (24) y[6] 010|0|0100 (68)
x[7] 000|1|1100 (28) y[7] 010|0|1000 (72)
=== WITH x[16] =====================================
element address element address
x[0] 000|0|0000 (0) y[0] 010|0|0000 (64)
x[1] 000|0|0100 (4) y[1] 010|0|0100 (68)
x[2] 000|0|1000 (8) y[2] 010|0|1000 (72)
x[3] 000|0|1100 (12) y[3] 010|0|1100 (76)
x[4] 000|1|0000 (16) y[4] 010|1|0000 (80)
x[5] 000|1|0100 (20) y[5] 010|1|0100 (84)
x[6] 000|1|1000 (24) y[6] 010|1|1000 (88)
x[7] 000|1|1100 (28) y[7] 010|1|1100 (92)
从上面可以很容易地看出,这些填充选择没有是最佳的。 x[16]
与最坏的情况基本相同,你有太多的填充。 x[9]
只解决 1/4 元素的问题,x[10]
解决 2/4 元素的问题,x[11]
解决 3/4 元素的问题。
所以,正如您所说,这些选项会导致代码中缓存未命中的百分比发生不同的变化,但没有一个是最佳的(即尽可能低的未命中率)。
唯一的最佳配置是您有任何填充,例如 x
以块索引 0
开头,y
以块索引 1
开头,然后是 {{1 }} 在块索引 1 处,x[4]
在块索引 0 处。要么这样,要么完全相反。因此,对于任何 N >= 1,数组 y[4]
大小的合适值是 x
,最小的是 8 * N + 4
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。