我想探索在新分配的结构(或类)的向量内多次解除引用数据的性能差异。
struct Foo
{
int val;
// some variables
}
std::vector<Foo*> vectorOfFoo;
// Foo objects are new-ed and pushed in vectorOfFoo
for (int i=0; i<N; i++)
{
Foo *f = new Foo;
vectorOfFoo.push_back(f);
}
在我迭代向量的代码部分中,我希望通过许多迭代器derefencing来增强引用的局部性,例如我经常执行双嵌套循环
for (vector<Foo*>::iterator iter1 = vectorOfFoo.begin(); iter!=vectorOfFoo.end(); ++iter1)
{
int somevalue = (*iter)->value;
}
显然,如果vectorOfFoo中的指针很远,我认为引用的局部性有些丢失。
如果在循环之前我在迭代之前对矢量进行排序,性能怎么样?我是否应该在重复去除引用方面有更好的表现?
我确保连续的'new'分配在内存布局中接近的指针吗?
答案 0 :(得分:2)
回答你的上一个问题:不,无法保证新分配内存的地方。分配可以分布在整个存储器中。根据记忆的当前碎片,你可能很幸运,他们有时彼此接近,但不能保证 - 或者,实际上,可以 - 给予。
答案 1 :(得分:2)
如果您想改善对象的引用位置,那么您应该查看Pool Allocation。
但如果没有剖析,那就毫无意义。
答案 2 :(得分:1)
这取决于很多因素。
首先,它取决于如何分配从向量指向的对象。如果它们被分配在不同的页面上,那么你无法帮助它,但修复分配部分和/或尝试使用软件预取。
您通常可以检查malloc提供的虚拟地址,但作为较大程序的一部分,单独分配的结果不是确定性的。因此,如果您想控制分配,您必须更聪明地完成。
对于NUMA系统,您必须确保您正在访问的内存是从运行进程的节点的物理内存中分配的。否则,无论你做什么,内存将来自另一个节点,除非将程序转移回其“home”节点,否则你无法做多少事情。
你必须检查从一个物体跳到另一个物体所需的步幅。预取器可以识别512字节窗口内的步幅。如果步幅更大,那么从提取器的角度来看,您正在谈论随机存储器访问。然后它将关闭不会从缓存中驱逐您的数据,并且您可以做的最好的事情是尝试使用软件预取。哪个可能有帮助(也可能没有帮助)(总是测试它)。
因此,如果对指针向量进行排序,使得它们指向的对象以相对较小的步幅一个接一个地连续放置 - 那么是的,您将通过使其对预取硬件更友好来提高内存访问速度。
您还必须确保对该向量进行排序不会导致更差的收益/失败率。
另外,根据您使用每个元素的方式,您可能希望一次性分配它们和/或将这些对象拆分为不同的较小结构并迭代较小的数据块。
无论如何,您必须在更改之前和之后测量整个应用程序的性能。这种优化是一项棘手的业务,即使从理论上讲,性能应该得到改善,事情也会变得更糟。有许多工具可用于帮助您分析内存访问。例如,cachegrind。英特尔的VTune也是如此。还有很多其他工具。所以不要猜测,试验并验证结果。