C++ 缓存友好矩阵,用于访问相邻单元 当前设计

C++ 缓存友好矩阵,用于访问相邻单元 当前设计,c++,caching,optimization,data-structures,C++,Caching,Optimization,Data Structures,在我的程序中,我有一个大的二维网格(1000 x 1000或更多),每个单元格包含一个小信息。 为了表示这个概念,选择非常简单:矩阵数据结构 相应的代码(在C++中)类似于: int w_size_grid = 1000; int h_size_grid = 1000; int* matrix = new int[w_size_grid * h_size_grid]; 你可以注意到我用了一个向量,但原理是一样的 为了访问网格元素,我们需要一个函数,该函数给定网格中的一个单元格(由(x,y)标识

在我的程序中,我有一个大的二维网格(1000 x 1000或更多),每个单元格包含一个小信息。 为了表示这个概念,选择非常简单:矩阵数据结构

相应的代码(在C++中)类似于:

int w_size_grid = 1000;
int h_size_grid = 1000;
int* matrix = new int[w_size_grid * h_size_grid];
你可以注意到我用了一个向量,但原理是一样的

为了访问网格元素,我们需要一个函数,该函数给定网格中的一个单元格(由(x,y)标识),它返回存储在该单元格中的值

数学上:
f(x,y)->Z
显然:
f:Z^2->Z
其中
Z

这可以通过线性函数实现。这里是C++代码表示:

int get_value(int x, int y) {
  return matrix[y*w_size_grid + x];
}
附加实施说明 实际上,设计概念需要一种“循环连续网格”:单元的访问指数可以超出网格本身的限制

这意味着,例如,特定情况:
get_值(-1,-1)仍然有效。该函数只返回与
get_值相同的值(w_size_grid-1,h_size_grid-1)

实际上,这在实施过程中并不是一个问题:

int get_value(int x, int y) {
  adjust_xy(&x, &y);  // modify x and y in accordance with that rule.
  return matrix[y*w_size_grid + x];
}
无论如何,这只是一个额外的注意事项,以使场景更加清晰


有什么问题? 上述问题非常简单,设计和实现都非常简单

我的问题在于矩阵的更新频率很高。读取矩阵中的每个单元格,并可能使用新值进行更新

显然,根据缓存友元设计,矩阵的解析是通过两个循环完成的:

for (int y = 0; y < h_size_grid; ++y) {
  for (int x = 0; x < w_size_grid; ++x) {
    int value = get_value(x, y);
  }
}
因此,代码直观地是:

for (int y = 0; y < h_size_grid; ++y) {
  for (int x = 0; x < w_size_grid; ++x) {
    int value = get_value(x, y);
    auto values = get_value_all_neighbours(x, y);  // values are 8 integer
  }
}
for(int y=0;y
函数
get\u value\u all\u neights(x,y)
将访问矩阵中相对于
y的上下一行。
由于矩阵中的一行相当大,因此它涉及缓存未命中,并且会弄脏缓存本身

问题 我终于向你们展示了这个场景和问题,我的问题是如何“解决”这个问题

使用一些额外的数据结构,或者重新组织数据,有没有一种方法可以利用缓存并避免所有这些丢失

一些个人的考虑 我的感觉引导我走向战略性数据结构

我考虑过重新实现向量中存储值的顺序,尝试将相邻的单元格存储在连续索引中

这意味着
get\u value
不再使用线性函数。 经过一些思考,我相信不可能找到这个非线性函数


我还考虑了一些额外的数据结构,如哈希表,用于存储每个单元格的相邻值,但我认为这在空间上是一种过度消耗,可能在CPU周期上也是如此。

事实上,数据结构并非微不足道,尤其是在涉及优化时

有两个主要问题需要解决:数据内容和数据使用。数据内容是数据中的值,用法是数据的存储、检索方式和频率

数据内容 是否访问了所有的值?经常吗?
不经常访问的数据可以推送到较慢的介质,包括文件。为频繁访问的数据保留快速内存(如数据缓存)

数据相似吗?有模式吗?
对于表示大量数据相同的矩阵(如稀疏矩阵或下三角矩阵),有一些替代方法。对于大型矩阵,执行一些检查并返回常量值可能更快或更有效

数据使用 数据使用是决定数据有效结构的关键因素。即使是矩阵

例如,对于频繁访问的数据,贴图或关联数组可能更快

有时,使用许多局部变量(即寄存器)可能更有效地处理矩阵数据。例如,首先用值加载寄存器(数据提取),使用寄存器进行操作,然后将寄存器存储回内存。对于大多数处理器来说,寄存器是保存数据最快的介质

数据可能需要重新排列,以有效利用数据缓存和缓存线。数据缓存是一个非常靠近处理器核心的高速内存区域。缓存线是数据缓存中的一行数据。一个高效的矩阵可以为每个缓存线容纳一行或多行

更有效的方法是对数据缓存线执行尽可能多的访问。宁愿减少重新加载数据缓存的需要(因为索引超出范围)

这些操作可以独立执行吗?
例如,缩放矩阵,其中每个位置乘以一个值。这些操作不依赖于矩阵的其他单元格。这允许并行执行操作。如果它们可以并行执行,那么它们可以委托给具有多个核心的处理器(如GPU)

总结
当一个程序是数据驱动的时候,数据结构的选择并不简单。在为数据选择结构以及如何对齐数据时,内容和用法是重要的因素。使用和性能要求还将决定访问数据的最佳算法。互联网上已经有很多关于优化数据驱动应用程序和最佳使用数据缓存的文章

假设您确实存在无法避免的缓存未命中问题(请参阅此处的其他答案)

您可以使用缓存管理器以缓存友好的方式组织数据。本质上,空间填充曲线将体积或平面(如矩阵)映射到线性表示
for (int y = 0; y < h_size_grid; ++y) {
  for (int x = 0; x < w_size_grid; ++x) {
    int value = get_value(x, y);
    auto values = get_value_all_neighbours(x, y);  // values are 8 integer
  }
}