C 优化缓存线的二维数组索引
我正试图优化大型2D(也就是说,一维被视为2D)字节数组的索引,以最大限度地增加从64字节大小的同一缓存线连续查找的数量。每个查找都与上一个查找不同,在水平和垂直移动之间交替进行。运动是正的还是负的可以被视为随机的(实际上它遵循朗顿的蚂蚁规则字符串RLR,但我不认为信息是严格相关的),这意味着路径混乱地蜿蜒,倾向于在相同的一般区域停留相当长的时间 对于一次对一行进行常规索引,水平移动可能在同一缓存线内,但垂直移动永远不会。我的解决方案是以8x8块为阵列编制索引,下面是一个示例,假设6x6阵列的缓存线大小为9:C 优化缓存线的二维数组索引,c,optimization,cpu-cache,C,Optimization,Cpu Cache,我正试图优化大型2D(也就是说,一维被视为2D)字节数组的索引,以最大限度地增加从64字节大小的同一缓存线连续查找的数量。每个查找都与上一个查找不同,在水平和垂直移动之间交替进行。运动是正的还是负的可以被视为随机的(实际上它遵循朗顿的蚂蚁规则字符串RLR,但我不认为信息是严格相关的),这意味着路径混乱地蜿蜒,倾向于在相同的一般区域停留相当长的时间 对于一次对一行进行常规索引,水平移动可能在同一缓存线内,但垂直移动永远不会。我的解决方案是以8x8块为阵列编制索引,下面是一个示例,假设6x6阵列的缓
24 25 26 33 34 35
21 22 23 30 31 32
18 19 20 27 28 39
6 7 8 15 16 17
3 4 5 12 13 14
0 1 2 9 10 11
对于3x3块,它不会显示得很好,但它应该允许缓存线被更多地重用:
.
.
.
56 57 58 59 60 61 62 63
48 49 50 51 52 53 54 55
40 41 42 43 44 45 46 47
32 33 34 35 36 37 38 39
24 25 26 27 28 29 30 31
16 17 18 19 20 21 22 23
8 9 10 11 12 13 14 15
0 1 2 3 4 5 6 7 ...
我已经用普通索引和这种索引进行了基准测试,这种索引速度较慢。这可能是因为它必须做更多的工作来计算出所需的索引(它处于一个紧密的循环中,请参阅此处了解正常的索引版本:)。我不能完全排除这种索引更有效率的可能性(制定新索引可能是可以优化的,考虑缓存对我来说是新的,我可能做得不好)
1) 理智检查:我想做的是明智的,它可能会起作用吗?它会在不同的条件下工作吗
2) Longshot:是否有一个神奇的gcc编译器标志为您重新排序索引,以尝试优化2D数组而不是1D
3) 我可以(或者我需要)做些什么来尝试在CPU中保留某些缓存线吗?目前我假设最新的数据会一直保存到被覆盖
4) 如果您有更好的解决方案,请描述它
64位linux、gcc、i5-2500k
编辑:结果是:1)这种思维方式不明智,2)不适用,3)请参见接受答案,4)请参见接受答案我看不到最大限度地连续使用一条缓存线的理由。缓存不会“一次一行”运行,通常重复使用一条缓存线比使用缓存中的任何缓存线都没有优势 更好的目标是最大化从一级缓存中的一条缓存线提供的访问次数,而不需要从较慢的缓存或内存中获取。只要访问“命中”缓存中当前的一行,我们就不关心它是哪个缓存线 i5-2500K是一款沙桥处理器。Sandy Bridge一级数据缓存为32 KiB,具有8路关联性,具有64字节缓存线。这意味着32768字节缓存有512行,它们被组织成64组,每组8行。每个内存地址映射到一个集合,如下所示。在每个集合中,从最近在该集合中使用的缓存线中保留八条缓存线。(替换算法并非最近才使用,但它是一种有用的尝试,可能与最近使用的算法具有类似的结果。) 缓存查找的工作方式如下:
- 给定一个字节地址x,让t=floor(x/64)(由于缓存线的大小)
- 设s=t%64(选择集合)
- 检查集合s以查看它是否包含地址x处的字节
这并不是问题的完整解决方案,因为影响性能的因素很多。这可能没有用处,但可能很有趣 你可以用地址。它将对齐的8x8块映射到缓存线,因此只要您保持在一个对齐的8x8块内,您就始终使用相同的缓存线。但是当你从一个街区穿过另一个街区时,有时会发生奇怪的事情 从(x,y)对生成Z顺序地址有点烦人:
static uint Interleave(uint x, uint y)
{
y = (y | (y << 1)) & 0x00FF00FF;
y = (y | (y << 2)) & 0x0F0F0F0F;
y = (y | (y << 4)) & 0x33333333;
y = (y | (y << 8)) & 0x55555555;
x = (x | (x << 1)) & 0x00FF00FF;
x = (x | (x << 2)) & 0x0F0F0F0F;
x = (x | (x << 4)) & 0x33333333;
x = (x | (x << 8)) & 0x55555555;
return x | (y << 1);
}
你甚至可以加入一些边界检查。我有使增量/减量饱和的例程,但我只有大约90%确定它们能工作。将模2的幂包装起来是很简单的,只需对结果进行二进制运算即可
Z坐标的寻址
static uint IncX(uint z)
{
uint xsum = (z | 0xAAAAAAAA) + 1;
return (xsum & 0x55555555) | (z & 0xAAAAAAAA);
}
static uint IncY(uint z)
{
uint ysum = (z | 0x55555555) + 2;
return (ysum & 0xAAAAAAAA) | (z & 0x55555555);
}
static uint DecX(uint z)
{
uint xsum = (z & 0x55555555) - 1;
return (xsum & 0x55555555) | (z & 0xAAAAAAAA);
}
static uint DecY(uint z)
{
uint ysum = (z & 0xAAAAAAAA) - 2;
return (ysum & 0xAAAAAAAA) | (z & 0x55555555);
}