使用std :: vector与std :: list时,Linux内存使用率排在首位

时间:2012-12-31 21:52:16

标签: c++ linux memory heap allocator

我注意到Linux中有关top报告的内存使用情况(RES)的一些有趣行为。我附加了以下程序,它在堆上分配了几百万个对象,每个对象都有一个大约1千字节的缓冲区。指向这些对象的指针由std::liststd::vector跟踪。我注意到的有趣行为是,如果我使用std::listtop报告的内存使用量在睡眠期间永远不会改变。但是,如果我使用std::vector,则在这些睡眠期间内存使用率将降至接近0。

我的测试配置是:
Fedora Core 16
内核3.6.7-4
g ++版本4.6.3

我已经知道的事情:
1. std :: vector将根据需要重新分配(大小加倍) 2. std :: list(我相信)一次分配其元素1 3. std :: vector和std :: list都默认使用std :: allocator来获取它们的实际内存
该计划没有泄漏; valgrind宣称不可能有泄漏。

我感到困惑的是:
1. std :: vector和std :: list都使用std :: allocator。即使std :: vector正在进行批量重新分配,std :: allocator也不会以几乎相同的方式将内存分发给std :: list和std :: vector?毕竟这个程序是单线程的 2.我在哪里可以了解Linux内存分配的行为。我听说过关于Linux保持RAM分配给进程的声明,即使它释放了它,但我不知道这种行为是否得到保证。为什么使用std :: vector会影响这种行为呢?

非常感谢您阅读本文;我知道这是一个相当模糊的问题。我在这里寻找的'答案'是这个行为是'定义'还是我可以找到它的文档。

#include <string.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#include <list>
#include <iostream>
#include <memory>

class Foo{
public:
    Foo()
    {
        data = new char[999];
        memset(data, 'x', 999);
    }

    ~Foo()
    {
        delete[] data;
    }

private:
    char* data;

};

int main(int argc, char** argv)
{
    for(int x=0; x<10; ++x)
    {
        sleep(1);
        //std::auto_ptr<std::list<Foo*> > foos(new std::list<Foo*>);
        std::auto_ptr<std::vector<Foo*> > foos(new std::vector<Foo*>);
        for(int i=0; i<2000000; ++i)
        {
            foos->push_back(new Foo());
        }
        std::cout << "Sleeping before de-alloc\n";
        sleep(5);
        while(false == foos->empty())
        {
            delete foos->back();
            foos->pop_back();
        }
    }
    std::cout << "Sleeping after final de-alloc\n";
    sleep(5);
}

4 个答案:

答案 0 :(得分:3)

释放内存是在“块”的基础上完成的。很有可能当你使用list时,内存会被分成几个小小的碎片。

当你使用向量分配时,所有元素都存储在一个大块中,因此内存释放代码很容易说“Golly,我在这里有一个非常大的自由区域,我将发布它回到操作系统“。在向量增长时,内存分配器进入“大块模式”也是完全可能的,它使用与“小块模式”不同的分配方法 - 比如你分配超过1MB,内存分配代码可能会看到作为开始使用不同策略的好时机,只需要求操作系统获得“完美契合”的内存。这个大块很容易在被释放时释放回操作系统。

如果你要添加一个列表,那么你总是要求一点点,所以分配器使用不同的策略来请求大块,然后给出一小部分。确保块中的所有块都已被释放既困难又耗时,因此分配器可能“不打扰” - 因为可能存在“仍在使用中”的某些区域,然后它可以'无论如何都要被释放。

我还要补充一点,使用“top”作为内存度量并不是一种特别准确的方法,并且非常不可靠,因为它在很大程度上取决于操作系统和运行时库的功能。属于进程的内存可能不是“驻留”,但进程仍然没有释放它 - 它只是“不存在于实际内存中”(而是在交换分区中出现!)

对于你的问题“这是在某处定义的”,我认为这是C / C ++库源码鳕鱼定义它的意义。但它并没有在某种意义上定义,它写的是“这就是它的工作方式,我们保证永远不会改变它”。作为glibc和libstdc ++提供的库不会说,随着新技术和想法的发明,它们将改变malloc,free,new和delete的内部结构 - 有些可能会让事情变得更好,有些可能会让事情变得更糟场景。

正如评论中指出的那样,内存未锁定到进程。如果内核认为内存更好地用于其他东西[并且内核在这里是无所不能的],那么它将从一个正在运行的进程中“窃取”内存并将其交给另一个进程。特别是长时间未被“触摸”的记忆。

答案 1 :(得分:2)

  

1。 std :: vector和std :: list都使用std :: allocator。即使std :: vector正在进行批量重新分配,也不会是std :: allocator   以几乎相同的方式将内存分发给std :: list和   的std ::载体?毕竟这个程序是单线程的。

嗯,有什么区别?

  • std::list逐个分配节点(除了Foo *之外,每个节点还需要两个指针)。此外,它永远不会重新分配这些节点(这由list的迭代器失效要求保证)。因此,std::allocator将从基础机制请求一系列固定大小的块(可能是malloc,然后将使用sbrkmmap系统调用。这些固定大小的块可能比列表节点大,但如果是这样,它们都将是std::allocator使用的相同默认块大小。

  • std::vector分配一个连续的指针块,没有任何记账开销(这都在向量父对象中)。每当push_back溢出当前分配时,向量将分配一个新的,更大的块,将所有内容移动到新块,并释放旧块。现在,新块的大小将类似于旧块的大小(或1.6倍,或其他任何大小),这是保持push_back分摊的常量时间保证所需的。所以,很快,我希望它要求的大小超过std::allocator的任何合理的默认块大小。

因此,有趣的交互是不同的:一个介于std::vector和分配器的底层机制之间,另一个介于std::allocator本身和底层机制之间。

  

2。我在哪里可以了解Linux内存分配的行为。我听说过关于Linux保持RAM分配给进程的声明   即使它释放了它,但我不知道这种行为是否存在   保证。为什么使用std :: vector会影响这种行为呢?

您可能会关注以下几个级别:

  1. 容器自己的分配模式:希望如上所述
    • 请注意,在实际应用程序中,使用容器的方式同样重要
  2. std::allocator本身,可以为小分配提供一层缓冲
    • 我不认为这是标准所要求的,因此它特定于您的实施
  3. 底层分配器,取决于您的std::allocator实现(例如,它可能是malloc,但是由您的libc实现)
  4. 内核使用的VM方案及其与任何系统调用(3)的交互最终使用

  5. 在您的特定情况下,我可以想到一个可能的解释,该向量显然释放了比列表更多的内存。

    考虑到向量最终只有一个连续的分配,并且许多Foo s也将被连续分配。这意味着当你释放所有这些内存时,很容易发现大多数底层页面都是真正免费的。

    现在考虑列表节点分配与Foo实例以1:1交错。即使分配器进行了一些批处理,看起来堆可能比std::vector情况下更加分散。因此,当您释放已分配的记录时,需要做一些工作来确定底层页面现在是否空闲,并且没有特别的理由预期会发生这种情况(除非后续的大型分配鼓励合并堆记录)。

答案 2 :(得分:1)

答案是malloc&#34; fastbins&#34;优化。 std :: list创建微小的(少于64个字节)分配,当它释放它们时,它们实际上没有被释放 - 而是转到fastblock池。 此行为意味着即使清除列表后堆仍保持碎片状态,因此它不会返回到系统。

您可以使用malloc_trim(128 * 1024)强行清除它们。 或者使用mallopt(M_MXFAST,0)来完全禁用fastbins。

如果你真的不再需要记忆,那么我发现第一个解决方案更正确。

答案 3 :(得分:0)

较小的块通过brk并调整数据段和不断的分裂和融合以及更大的块mmap过程受到的影响稍微小一些。更多信息(PDF

也是ptmalloc源代码。