巨大的std :: vector <std :: vector>在销毁时不会释放所有内存

qui*_*uss 22 c++ memory vector

当使用非常大的向量向量时,我们发现没有释放部分内存.

#include <iostream>
#include <vector>
#include <unistd.h>

void foo()
{
    std::vector<std::vector<unsigned int> > voxelToPixel;
    unsigned int numElem = 1<<27;
    voxelToPixel.resize( numElem );

    for (unsigned int idx=0; idx < numElem; idx++)
        voxelToPixel.at(idx).push_back(idx);

}

int main()
{
    foo();
    std::cout << "End" << std::endl;
    sleep(30);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这将留下大约4GB的内存,直到进程结束.

如果我们将for行更改为

for (unsigned int idx=0; idx < numElem; idx++)
    voxelToPixel.at(0).push_back(idx);
Run Code Online (Sandbox Code Playgroud)

记忆被释放了.

使用gcc-4.8Linux机器上.我们习惯于htop在具有100 GB RAM的计算机上跟踪内存使用情况.您将需要大约8 GB的RAM来运行代码.你能重现这个问题吗?关于为什么会发生这种情况的任何想法?

编辑:我们已经看到,这不会发生在Mac(任何一个gccclang).此外,在linux中,如果我们调用foo两次(但第三次再次发生),则释放内存.

Mik*_*our 27

小分配(我认为默认情况下高达128kb)由进程内堆管理,并且在取消分配时不会返回给操作系统; 它们将返回堆中以便在进程中重用.较大的分配直接来自OS(通过调用mmap),并在解除分配时返回到OS.

在第一个示例中,每个向量只需要为单个向量分配足够的空间int.您有一亿个小分配,其中任何一个都不会返回给操作系统.

在第二个例子中,随着向量的增长,它将进行多种不同大小的分配.有些小于mmap阈值,这些将保留在进程内存中; 但是,因为你只对一个向量执行此操作,所以这不会是一个巨大的数量.如果您在填充之前使用resizereserve分配每个向量的所有内存,那么您应该发现所有内存都返回给操作系统.

  • @quimnuss没有必要强制进程释放未使用的堆内存.操作系统已经可以取走物理内存,虚拟内存很便宜. (2认同)
  • 在Linux中,没有仔细分配内存.你写入允许堆的虚拟内存区域,Linux找到一些RAM来支持它.您可以通过使用`brk`系统调用设置数据段的大小来通知Linux您已经完成了整个内存区域的使用.C++分配器将为您处理此问题.如果您没有释放内存,但也暂时不使用它,Linux会将其分页到磁盘,让其他进程或磁盘缓存使用RAM. (2认同)