我正在努力抓住面向数据的设计以及如何最好地编写缓存。基本上有两种情况我无法确定哪种更好以及为什么 - 拥有对象矢量或带有对象原子数据的几个向量是否更好?
A)对象矢量示例
struct A
{
GLsizei mIndices;
GLuint mVBO;
GLuint mIndexBuffer;
GLuint mVAO;
size_t vertexDataSize;
size_t normalDataSize;
};
std::vector<A> gMeshes;
for_each(gMeshes as mesh)
{
glBindVertexArray(mesh.mVAO);
glDrawElements(GL_TRIANGLES, mesh.mIndices, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
....
}
B)具有原子数据的载体
std::vector<GLsizei> gIndices;
std::vector<GLuint> gVBOs;
std::vector<GLuint> gIndexBuffers;
std::vector<GLuint> gVAOs;
std::vector<size_t> gVertexDataSizes;
std::vector<size_t> gNormalDataSizes;
size_t numMeshes = ...;
for (index = 0; index++; index < numMeshes)
{
glBindVertexArray(gVAOs[index]);
glDrawElements(GL_TRIANGLES, gIndices[index], GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
....
}
哪一个更有内存效率和缓存友好性,从而减少缓存未命中和性能,以及为什么?
答案 0 :(得分:5)
根据您所讨论的缓存级别的某些变化,缓存的工作方式如下:
要问的问题是天真的:
所以,我希望B对于此代码更快。但是:
struct
中删除大多数数据成员来加速A.那样做吧。据推测,事实上它不是对程序中数据的唯一访问,而其他访问可能会以两种方式影响性能:它们实际占用的时间,以及它们是否使用您需要的数据填充缓存。答案 1 :(得分:1)
我知道这部分是基于意见的,而且它可能是一个过早优化的情况,但你的第一个选择肯定是最好的美学。这是一个矢量与六个 - 我眼中没有比赛。
对于缓存性能,它应该更好。这是因为替代方案需要访问两个不同的向量,每次渲染网格时都会分割内存访问。
使用结构方法,网格本质上是一个自包含的对象,并且正确地暗示与其他网格无关。绘图时,只访问那个网格物体,当渲染所有网格物体时,您可以以缓存友好的方式一次执行一个。是的,你会更快地吃缓存,因为你的矢量元素更大,但你不会参与竞争。
您可能会在以后使用此表示找到其他好处。 ie 如果要存储有关网格的其他数据。在更多向量中添加额外数据会很快混乱您的代码并增加制造愚蠢错误的风险,而对结构进行更改则是微不足道的。
答案 2 :(得分:1)
我建议使用perf或oprofile进行性能分析,并将结果发布到此处(假设您正在运行linux),包括迭代的元素数量,总计迭代次数以及你测试的硬件。
如果我不得不猜测(这只是一个猜测),我怀疑第一种方法可能会更快,因为每个结构中的数据的位置,并希望操作系统/硬件可以为您预取其他元素。但同样,这将取决于缓存大小,缓存行大小和其他方面。
定义“更好”也很有趣。您是否正在寻找处理N个元素的总时间,每个样本的低差异,最小的缓存未命中(这将受到系统上运行的其他进程的影响)等。
不要忘记使用STL向量,你也受分配器的支配......例如它可以随时决定重新分配数组,这将使您的缓存无效。如果可以的话,尝试隔离的另一个因素是!
答案 3 :(得分:0)
取决于您的访问模式。您的第一个版本是AoS (array of structures),第二个版本是SoA (structure of arrays)。
SoA倾向于使用更少的内存(除非你存储如此少的元素,使得数组的开销实际上是非常重要的)如果有任何类型的结构填充,你可以使用#39 ; d通常进入AoS表示。它也往往是一个更大的PITA代码,因为你必须维护/同步并行数组。
AoS往往优于随机访问。作为示例,为简单起见,假设每个元素都适合高速缓存行并且被正确对齐(例如,64字节大小和对齐)。在这种情况下,如果您随机访问nth
元素,则会在单个缓存行中获取该元素的所有相关数据。如果您使用SoA并将这些字段分散在不同的数组中,则必须将内存加载到多个缓存行中才能加载该元素的数据。而且因为我们以随机模式访问数据,所以我们根本不会从空间局部性中受益,因为我们将要访问的下一个元素可能完全存在于内存中。< / p>
然而,SoA往往优于顺序访问,主要是因为它通常在整个顺序循环中首先加载到CPU缓存中的数据较少,因为它排除了结构填充和冷场。冷场,我的意思是你不需要在特定的顺序循环中访问的字段。例如,物理系统可能不关心粒子如何看待用户的粒子场,如颜色和精灵手柄。这是无关紧要的数据。它只关心粒子位置。 SoA允许您避免将不相关的数据加载到缓存行中。它允许您同时将相关数据加载到缓存行中,因此您最终可以使用SoA减少强制缓存未命中(以及足够大的数据的页面错误)。
这也仅涵盖了内存访问模式。使用SoA代表,您也倾向于编写更有效和更简单的SIMD指令。但同样,它主要适用于顺序访问。
您也可以混合使用这两个概念。您可以将AoS用于经常以随机访问模式一起访问的热字段,然后提升冷字段并将它们并行存储。