我试图很好地掌握面向数据的设计以及如何在考虑缓存的情况下进行最佳编程。基本上有两种情况我无法完全确定哪个更好以及为什么 - 是拥有一个对象向量更好,还是拥有对象原子数据的多个向量更好?
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);
....
}
哪一种内存效率更高且缓存友好,从而减少缓存未命中并提高性能,为什么?
根据您所讨论的缓存级别的不同,缓存的工作原理如下:
- 如果数据已经在缓存中,那么访问速度很快
- 如果数据不在缓存中,那么您会产生成本,但是整个缓存行(或页面,如果我们谈论 RAM 与交换文件而不是缓存与 RAM)都会被带入缓存,因此靠近丢失地址的访问将不要错过。
- 如果幸运的话,内存子系统将检测顺序访问并预取它认为您将需要的数据。
所以天真地要问的问题是:
- 发生了多少次缓存未命中? -- B 获胜,因为在 A 中,您为每条记录获取了一些未使用的数据,而在 B 中,您只在迭代结束时获取了一个小的舍入误差。因此,为了访问所有必要的数据,假设有大量记录,B 会获取较少的缓存行。如果记录数量微不足道,那么缓存性能可能与代码的性能几乎没有关系或根本没有关系,因为使用足够少量数据的程序会发现它始终都在缓存中。
- 访问是顺序的吗? -- 两种情况都是如此,尽管在情况 B 中可能更难检测到,因为有两个交错序列而不是只有一个。
所以,我有点希望 B 更快对于这段代码。然而:
- 如果这是对数据的唯一访问,那么您可以通过从 A 中删除大部分数据成员来加速 A
struct
。所以就这么做吧。事实上,它可能不是对程序中数据的唯一访问,其他访问可能会以两种方式影响性能:它们实际花费的时间,以及它们是否用您需要的数据填充缓存。
- 我的预期和实际发生的情况经常是不同的事情,如果你有能力进行测试,那么依赖猜测就没有什么意义。在最好的情况下,顺序访问意味着任一代码中都没有缓存未命中。测试性能requires没有特殊的工具(尽管它们可以让事情变得更容易),只是一个带有秒针的时钟。必要时,用手机充电器制作一个钟摆。
- 有一些并发症我忽略了。根据硬件的不同,如果 B 不幸运,那么在最低缓存级别,您可能会发现对一个向量的访问正在逐出对另一向量的访问,因为相应的内存恰好使用缓存中的相同位置。这会导致两次缓存未命中每条记录。这只会发生在所谓的“直接映射缓存”上。 “双向缓存”或更好的方式可以挽救局面,通过允许两个向量的块共存,即使它们在缓存中的第一首选位置相同。我不认为PC硬件通常使用直接映射缓存,但我不确定并且我对GPU了解不多。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)