【问题标题】:Memory Demands: Heap vs Stack in C++内存需求:C++ 中的堆与堆栈
【发布时间】:2014-08-20 05:48:17
【问题描述】:

所以我今晚有一个奇怪的经历。

我正在使用 C++ 编写一个程序,该程序需要某种方式从文件中读取一长串简单数据对象并将它们存储在主内存中,大约 400,000 个条目。对象本身类似于:

class Entry
{
public:
    Entry(int x, int y, int type);
    Entry(); ~Entry();
    // some other basic functions
private:
    int m_X, m_Y;
    int m_Type;
};

很简单,对吧?好吧,因为我需要从文件中读取它们,所以我有一些循环,比如

Entry** globalEntries;
globalEntries = new Entry*[totalEntries];
entries = new Entry[totalEntries];// totalEntries read from file, about 400,000
for (int i=0;i<totalEntries;i++)
{
    globalEntries[i] = new Entry(.......);
}

当我在任务管理器上跟踪该程序时,该程序增加了大约 25 到 35 兆字节。堆栈分配的简单更改:

Entry* globalEntries;
globalEntries = new Entry[totalEntries];
for (int i=0;i<totalEntries;i++)
{
    globalEntries[i] = Entry(.......);
}

突然间它只需要 3 兆字节。为什么会这样?我知道指针对象对它们有一点额外的开销(指针地址为 4 个字节),但这不足以产生很大的不同。可能是因为程序分配内存效率低下,最终在分配的内存之间出现了大块未分配的内存?

【问题讨论】:

  • 看起来您正在泄漏entries,并且您可能正在泄漏其他所有内容,但没有足够的代码可以确定。
  • 这不是泄漏。我进入调试器以确保分配内存的代码块不会被多次调用。每个条目仅在循环中按顺序分配一次。我有所有条目的可视化表示。
  • 这并不意味着您没有泄漏。如果你分配了东西但你不删除它,你就有泄漏。请注意,您似乎甚至没有使用可能泄漏的东西。
  • 在运行开始时调用一次分配内存,然后在整个程序中不断使用这些条目。
  • 您的代码没有显示。它显示您使用newnew [](其中一些您不使用)分配东西,并且从不调用deletedelete []

标签: c++ memory-management heap-memory stack-memory


【解决方案1】:

您的代码有误,或者我看不出它是如何工作的。使用new Entry [count],您创建了一个新的Entry 数组(类型为Entry*),但您将其分配给Entry**,所以我假设您使用了new Entry*[count]

接下来您要做的是在堆上创建另一个新的 Entry 对象,并将其存储在 globalEntries 数组中。因此,您需要 400.000 个指针 + 400.000 个元素的内存。在 64 位机器上,400.000 个指针占用 3 MiB 的内存。此外,您有 400.000 个单个条目分配,它们都需要sizeof (Entry) 加上可能需要更多内存(对于内存管理器——它可能必须存储分配的大小、关联的池、对齐/填充等)这些额外的记账内存可以快速增加。

如果您将第二个示例更改为:

 Entry* globalEntries;
 globalEntries = new Entry[count];
 for (...) {
     globalEntries [i] = Entry (...);
 }

内存使用应该等于堆栈方法。

当然,理想情况下你会使用std::vector&lt;Entry&gt;

【讨论】:

  • 对。对不起,我滑倒了;我没有直接从我的代码中复制它,但这就是我所拥有的。
  • 64 位机器上的 400 000 个指针是 3 200 000 字节,即 3.2 兆字节,而不是 30 兆字节。稍后应用相同的数学。我认为您移动了小数点。
  • 三兆字节的评论更多的是一般估计。尽管我将类型更改为 char,坐标更改为 16 位整数,但它可能接近 5 个。
  • 删除了最后一段然后:)
  • 它仍然没有考虑到内存使用量的 5-8 倍差异。
【解决方案2】:

首先,如果不指定您正在观看的具体列,任务管理器中的数字没有任何意义。在现代操作系统上,甚至很难定义您对“已用内存”的含义——我们是在谈论私有页面吗?工作组?只有留在 RAM 中的东西?保留但未提交的内存是否计数?谁为进程之间共享的内存买单?是否包含内存映射文件?

如果您正在查看一些有意义的指标,则不可能看到使用了 3 MB 的内存 - 您的对象至少有 12 个字节(假设 32 位整数且没有填充),因此 400000 个元素将需要大约 4.58 MB。此外,如果它与堆栈分配一起使用,我会感到惊讶 - VC++ 中的默认堆栈大小为 1 MB,您应该已经发生堆栈溢出。

无论如何,期望不同的内存使用是合理的:

  • 堆栈(大部分)是从一开始就分配的,所以这是您名义上消耗的内存,即使没有真正将它用于任何事情(实际上虚拟内存和自动堆栈扩展使这有点复杂,但它“足够真实”) ;
  • CRT 堆对任务管理器是不透明的:它看到的只是操作系统给进程的内存,而不是 C 堆“真正”使用的内存;堆的增长(向操作系统请求内存)超过了为进一步的内存请求做好准备所需的严格要求 - 所以你看到的是它准备放弃多少内存而无需进一步的系统调用;
  • 您的“单独分配”方法开销很大。使用new Entry[size] 获得的全连续数组花费size*sizeof(Entry) 字节,加上堆簿记数据(通常是几个整数大小的字段);分离分配方法的成本至少为size*sizeof(Entry)(所有“裸元素”的大小)加上size*sizeof(Entry *)(指针数组的大小)加上size+1乘以每个分配的成本。如果我们假设一个 32 位架构,每次分配的成本为 2 个整数,您很快就会发现这会花费 size*24+8 字节的内存,而不是堆中连续数组的 size*12+8
  • 堆通常确实会放弃实际上不是您要求的大小的块,因为它管理固定大小的块;因此,如果您分配像这样的单个对象,您可能还需要为一些额外的填充支付 - 假设它有 16 个字节的块,那么您通过单独分配它们为每个元素额外支付 4 个字节;这会将内存估计移至size*28+8,即每个 12 字节元素的开销为 16 字节。

【讨论】:

  • 在任务管理器中,有一列写着内存(私有工作集)。这就是我使用的;我不知道我可以得到更准确的读数。
  • 谢谢。我怀疑它会是这样的。
猜你喜欢
  • 2011-08-15
  • 2011-05-28
  • 1970-01-01
  • 2012-01-18
  • 1970-01-01
  • 1970-01-01
  • 2013-07-06
  • 2011-07-16
相关资源
最近更新 更多