Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/63.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 优化缓存线的二维数组索引_C_Optimization_Cpu Cache - Fatal编程技术网

C 优化缓存线的二维数组索引

C 优化缓存线的二维数组索引,c,optimization,cpu-cache,C,Optimization,Cpu Cache,我正试图优化大型2D(也就是说,一维被视为2D)字节数组的索引,以最大限度地增加从64字节大小的同一缓存线连续查找的数量。每个查找都与上一个查找不同,在水平和垂直移动之间交替进行。运动是正的还是负的可以被视为随机的(实际上它遵循朗顿的蚂蚁规则字符串RLR,但我不认为信息是严格相关的),这意味着路径混乱地蜿蜒,倾向于在相同的一般区域停留相当长的时间 对于一次对一行进行常规索引,水平移动可能在同一缓存线内,但垂直移动永远不会。我的解决方案是以8x8块为阵列编制索引,下面是一个示例,假设6x6阵列的缓

我正试图优化大型2D(也就是说,一维被视为2D)字节数组的索引,以最大限度地增加从64字节大小的同一缓存线连续查找的数量。每个查找都与上一个查找不同,在水平和垂直移动之间交替进行。运动是正的还是负的可以被视为随机的(实际上它遵循朗顿的蚂蚁规则字符串RLR,但我不认为信息是严格相关的),这意味着路径混乱地蜿蜒,倾向于在相同的一般区域停留相当长的时间

对于一次对一行进行常规索引,水平移动可能在同一缓存线内,但垂直移动永远不会。我的解决方案是以8x8块为阵列编制索引,下面是一个示例,假设6x6阵列的缓存线大小为9:

 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处的字节
考虑行长度对这些缓存查找的影响。行长为65536字节时,数组元素a[i][j]和a[i+1][j]的地址相差65536字节。这意味着在上述查找过程中,它们的t值相差1024,而s值相同。因此,它们映射到同一集合

一旦算法上移或下移超过八行,而不改变缓存线外的列,所使用的单个缓存集就无法处理最近使用的九条缓存线。必须驱逐其中一人。实际上,缓存大小是八行(512字节),而不是512行(32768字节)

解决这一问题的一种简单方法是填充数组,使行的长度为65536+p字节,填充量为p。该数组将被分配额外的空间,并定义为比普通数组更长的行。额外的列通常可以忽略。不需要初始化它们;我们不关心它们的内容,只关心它们对地址的影响。(或者,如果程序方便的话,它们也可以用作补充数据。)

使用此填充,a[i][j]和a[i+1][j]之间的距离为65536+p字节,因此t值的差值为1024+p/64,s值的差值为p/64%64。例如,如果p为64或320,则s值的差值分别为1或5

我建议测试9*64的p。任何值64或更大都将确保连续行中同一列中的数组元素映射到不同的缓存集。然而,问题中描述的算法在列和行中都有偏移。因此,如果p很小,我们使连续行映射到不同缓存集的修复可能会被迂回到同一缓存集的列漫游所抵消。p的其他值也应尝试


这并不是问题的完整解决方案,因为影响性能的因素很多。

这可能没有用处,但可能很有趣

你可以用地址。它将对齐的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);
}