在存储指向对象的指针时改进引用的数据局部性
Improving data locality of reference when storing pointers to objects
我的理解是,由于预取,将对象直接存储到向量会比将指针存储到向量产生更好的性能。
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
如果您以某种方式安排动态分配的对象按顺序存在于堆中,那么这与在某个容器中存储指向它们的指针几乎无关。
你的表现问题很可能只是想象出来的。你真的衡量过表现吗?否则,也许您应该阅读更多关于堆碎片化的内容。
- 为什么将一个结构的引用设置为等于另一个结构只会更改一个数据成员?
- 拥有映射的现代方法,该映射可以指向或引用已在堆栈上分配的不同类型的数据
- 设计将引用元素移动到开头的数据结构.C++
- 具有引用数据成员的结构不是文本类型吗?
- 引用数据的 int 指针
- 如何在 c++ 中使用不同的键引用数据
- 为什么INVOKE总是取消引用数据成员,而不是在可能的情况下调用
- 具有引用数据类型的函数多态性
- 如果类具有引用数据成员,为什么编译器不合成默认赋值运算符
- 引用数据类型的 & 符号是否返回引用本身的地址或它所引用的值
- C++Jumbling类以增强引用的局部性
- 组合常量和非常量引用数据成员的单个类
- c++垃圾收集和循环引用数据
- 在存储指向对象的指针时改进引用的数据局部性
- 从成员函数引用数据成员时出错
- 基类静态引用数据成员上的C++对象切片行为的奇怪案例
- 具有引用数据成员的类的默认构造函数
- 引用数据成员和移动构造函数
- 引用数据类成员访问模式
- 如何处理赋值操作符和复制构造函数中的引用数据成员