使用动态分配的成员访问动态创建的对象的成本

时间:2010-05-26 00:46:59

标签: c++ optimization profiling memory-management

我正在构建一个应用程序,它将具有类型A的动态分配对象,每个对象都具有动态分配的成员(v),类似于下面的类

class A {
int a;
int b;
int* v;
};

其中:

  • v的内存将在构造函数中分配。
  • v将在创建类型A的对象时分配一次,并且永远不需要调整大小。
  • v的大小会因A的所有实例而异。

应用程序可能会有大量此类对象,并且主要需要通过CPU流式传输大量这些对象,但只需要对成员变量执行非常简单的计算。

  • 动态分配v可能意味着A及其成员v的实例不在内存中吗?
  • 可以使用哪些工具和技术来测试这种碎片是否是性能瓶颈?
  • 如果此类碎片是性能问题,是否有任何技术可以允许A和v在连续的内存区域中分配?
  • 或者是否有任何技术可以帮助内存访问,例如预取方案?例如,获取类型A的对象在预取v。
  • 的同时对其他成员变量进行操作
  • 如果在编译时可以知道v的大小或可接受的最大大小,那么用一个固定大小的数组替换v如int [max_length]会导致更好的性能吗?

目标平台是带有x86 / AMD64处理器,Windows或Linux操作系统的标准台式机,并使用GCC或MSVC编译器进行编译。

3 个答案:

答案 0 :(得分:2)

如果你有充分的理由关心表现......

  

动态分配v可能意味着A的实例及其成员v   是不是一起在记忆中?

如果它们都被分配了“新”,那么很可能它们将彼此靠近。但是,当前的记忆状态会极大地影响这种结果,这在很大程度上取决于你对记忆的所作所为。如果你只是一个接一个地分配了这些东西,那么后来几乎肯定会“几乎连续”。

如果A实例在堆栈中,则其“v”极不可能在附近。

  

如果此类碎片是性能问题,是否有任何可行的技术   允许A和v分配在连续的内存区域吗?

为两者分配空间,然后将新的位置放入该空间。它很脏,但通常应该有效:

char* p = reinterpret_cast<char*>(malloc(sizeof(A) + sizeof(A::v)));
char* v = p + sizeof(A);
A* a = new (p) A(v);

// time passes

a->~A();
free(a);
  

或者是否有任何技术可以帮助进行内存访问,例如预取方案?

预取是特定于编译器和平台的,但许多编译器都有可用的内在函数。请注意,如果您要立即尝试访问这些数据,那么预测将具有任何价值,您需要在需要数据之前进行数百次循环。也就是说,它可以加速巨大。内在函数看起来像__pf(my_a->v);

  

如果在编译时可以知道v的大小或可接受的最大大小   将int替换为固定大小的数组,如int v [max_length]导致更好   性能

也许。如果固定大小的缓冲区通常接近你需要的大小,那么它可能会大大提高速度。以这种方式访问​​一个 A实例总是会更快,但如果缓冲区不必要地巨大且很大程度上未使用,那么您将失去更多对象适合缓存的机会。即最好在缓存中包含更多小的对象,而不是让大量未使用的数据填满缓存。

具体取决于您的设计和性能目标。关于这一点的有趣讨论,在具有特定编译器的特定硬件上存在“真实世界”特定问题,请参阅The Pitfalls of Object Oriented Programming(这是PDF的Google Docs链接,可以找到PDF本身{{ 3}})。

答案 1 :(得分:1)

  

动态分配v是否可能意味着A及其成员v的实例不在内存中?

是的,很可能。

  

可以使用哪些工具和技术来测试这种碎片是否是性能瓶颈?

cachegrind,shark。

  

如果此类碎片是性能问题,是否有任何技术可以允许A和v在连续的内存区域中分配?

是的,您可以将它们分配在一起,但您应该首先看看它是否是一个问题。例如,您可以使用竞技场分配,或者编写自己的分配器。

  

或者是否有任何技术可以帮助内存访问,例如预取方案?例如,获取类型A的对象在预取v。

的同时对其他成员变量进行操作

是的,你可以这样做。最好的办法是分配彼此靠近的记忆区域。

  

如果在编译时可以知道v的大小或可接受的最大大小,那么将v替换为固定大小的数组,如int v [max_length]会导致更好的性能吗?

可能会也可能不会。它至少会使v成为结构成员本地。

  1. 编写代码。
  2. 资料
  3. 优化。

答案 2 :(得分:0)

如果你需要通过CPU传输大量的这些并且对每一个进行很少的计算,正如你所说,我们为什么要进行所有这些内存分配?

你能拥有一个结构副本和一个(大)缓冲区v,将你的数据读入它(二进制,速度),做你很少的计算,然后继续下一个。

该程序应该花费近100%的时间在I / O上。 如果在它运行时暂停几次,那么在调用像FileRead这样的系统例程的过程中几乎每次都会看到它。有些剖析器可能会向您提供此信息,但它们往往会对I / O时间过敏。