内存需求:c++中的堆与堆栈
Memory Demands: Heap vs Stack in C++
所以今晚我有一个奇怪的经历。
我正在用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字节的指针地址),但它不应该足以产生那么大的差异。可能是因为程序分配内存效率不高,最终在已分配的内存之间出现了未分配的内存块?
你的代码是错误的,或者我不知道这是如何工作的。对于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<Entry>
。
首先,如果没有指定您正在查看的列,那么任务管理器中的数字没有任何意义。在现代操作系统中,甚至很难定义您所说的"已用内存"是什么意思—我们是在谈论私有页面吗?工作集?只保留在内存中的东西吗?保留但未提交的内存计数吗?谁为进程之间的内存共享买单?是否包括内存映射文件?
如果你正在观察一些有意义的度量,不可能看到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 int,那么您很快就会发现,这需要size*24+8
字节的内存,而不是堆中连续数组的size*12+8
字节; - 堆通常会放弃不是你真正要求的大小的块,因为它管理固定大小的块;所以,如果你像这样分配单个对象,你可能还需要支付一些额外的填充——假设它有16个字节的块,你需要为每个元素额外支付4个字节,通过单独分配它们;这将内存估计移至
size*28+8
,即每个12字节元素的开销为16字节。
- 算法问题:查找从堆栈中弹出的所有序列
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- Visual Studio(或任何其他工具)能否将地址解释为调用堆栈(boost上下文)的开头
- 为什么调用堆栈数组会导致内存泄漏
- gdb错误:Backtrace已停止:上一帧与此帧相同(堆栈已损坏?)
- 在 leetcode 上提交解决方案时出现堆栈缓冲区溢出错误
- 我的 int main() 中出现堆栈溢出错误
- 堆栈和队列是否像C++中的数组一样传递?
- 拥有映射的现代方法,该映射可以指向或引用已在堆栈上分配的不同类型的数据
- 为什么 STL 容器适配器堆栈中的 top 返回常量引用?
- 从堆栈分配的原始指针构造智能指针
- 在函数范围内在堆栈上分配的数组在离开函数时是否总是被释放?
- 堆栈中大小变量输入错误 (C++)
- 堆栈问题(平衡表达式问题集)
- C++ 在堆栈中包含多态属性的类对象存储
- 部分专业化和对标准::void_t<>的需求
- 用于解析 win64 堆栈跟踪的命令行客户端(可以访问符号服务器)
- 在 C++ 中使用链表进行堆栈
- 计算C++堆栈需求;如何获得可读的交易品种表
- 内存需求:c++中的堆与堆栈