在存储指向对象的指针时改进引用的数据局部性

Improving data locality of reference when storing pointers to objects

本文关键字:引用 数据局部性 指针 对象 存储      更新时间:2023-10-16

我的理解是,由于预取,将对象直接存储到向量会比将指针存储到向量产生更好的性能。

std::vector<Object> container1; // The objects are stored sequentially. 
std::vector<Object*> container2; //The pointed to objects are all over the place.

有什么办法解决这个问题吗?

我正在考虑对指向的对象使用顺序容器。这样,指针和对象在内存中都是顺序的,但我不是缓存方面的专家,我甚至不确定这是否有助于提高性能。

图表中的一些测量,来自Pitfalls Presentation中的示例,显示了标准OO的效果,具有连续存储的局部性,然后是额外的改进重组数据结构,使其进一步扁平化并预取-http://seven-degrees-of-freedom.blogspot.co.uk/2009/12/pitfalls-of-object-oriented-programming.html?view=flipcard

OO编程的陷阱演示文稿是为游戏程序员编写的,我发现它非常清楚,它解释了在现代系统上使用多指针的传统对象的问题"OO的陷阱"-http://research.scee.net/files/presentations/gcapaustralia09/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf

此外,它不仅仅是关于缓存预取&服务器/桌面系统上的局部性,但也可能出现页面错误(可能是到磁盘/从磁盘(,这可能会使分散的碎片对象分配非常低效。其次,您可能想要使用多个核心,更新对象的问题是访问争用和锁定,因为每个对象都无法知道对对象的其他访问是"安全的"。

因此,为了增加局部性,可以尽可能紧凑地打包对象,并将它们的块放在内存中的同一页上(尤其是对于在缓存线上重叠的小项目(,这就是为什么在C中,用void *calloc(size_t nmemb, size_t size)预分配nmmb结构的阵列可能比用malloc void *malloc(size_t size); 一次预分配阵列效率高得多

现在假设大多数时候,你在大量对象上重复计算,比如对对象中的某个特定值求和,或者以某种方式对其进行转换以更新某些字段,然后你想将其组织起来,将数据打包在一起,尽可能多地放在一个缓存行上,而不是从RAM(或磁盘(加载所有你很少需要或使用的东西,类似的可能是标识字符串、链接指针或其他您暂时不需要的值。这些场实际上可以是独立的,所以求和可以与不同场的变换同时发生。或者,可以通过简单地将一个大的项目列表一分为二或四分之一来跨CPU进行处理,而无需争夺安全读取或更新每个对象所需的锁。你知道你感兴趣的数据是没有竞争的。

在这种情况下,你宁愿拥有并行阵列,你可以用CPU可以检测和预测的可预测预取顺序扫描,从而自动启动加载。没有指针链,项目的顺序是隐含的,因此您可以通过避免浪费的4/8字节指针来最大限度地提高数据密度。而且,您可以将锁争用减少到实际上需要来自多个线程的同时更新的字段。Herb Sutters机器体系结构演讲很有趣(但很长视频(-http://www.youtube.com/watch?feature=player_detailpage&v=L7zSU9HI-6I#t=897

当你开始这样思考时,你正在考虑面向数据的设计——什么是面向数据的开发?

如果它适合你的问题,它可以极大地提高性能,游戏程序员也会使用它http://gamedevelopment.tutsplus.com/articles/what-is-data-oriented-game-engine-design--cms-21052

这里还有另一个以前的答案,它有更详细的解释,并链接到一些解释现代记忆问题的非常详细的论文——什么是"记忆";"缓存友好";密码

我想您的选择应该取决于容器所有者和所包含对象之间的关系类型。若容器所有者管理它们的生存期,那个么您就可以自由使用对象容器,并且可能会被鼓励使用对象容器。如果没有直接的管理关系,例如在侦听器容器的情况下,您应该使用指针容器。对于性能问题,我真的不认为这是一个好的观点。

您将无法以任何合理的方式对std::vector执行此操作。这个容器就是不适合这个用途。考虑一下脑海中出现的几个问题:

container2.push_back(nullptr);
Object o;
container2.push_back(&o);
Object *ptr = new Object;
container2.push_back(ptr);
container2.push_back(ptr); // added twice

如果您以某种方式安排动态分配的对象按顺序存在于堆中,那么这与在某个容器中存储指向它们的指针几乎无关。

你的表现问题很可能只是想象出来的。你真的衡量过表现吗?否则,也许您应该阅读更多关于堆碎片化的内容。