【问题标题】:why compiler is defering std::list deallocation?为什么编译器推迟 std::list 释放?
【发布时间】:2011-11-08 12:00:24
【问题描述】:

我有以下代码使用 std::list 容器测试内存释放:

#include <iostream>
#include <list>
#include <string>

#include <boost/bind.hpp>

/* count of element to put into container
 */
static const unsigned long SIZE = 50000000;

/* element use for test
 */
class Element
{
public:
    Element()
    : mId(0)
    {}
    Element( long id )
    : mId(id)
    {}
    virtual ~Element()
    {
    }
    inline long getId() const
    {
        return this->mId;
    }

    inline bool operator<( const Element & rightOperand ) const
    {
        return this->mId < rightOperand.mId;
    }

    inline bool isEven() const
    {
        return 0 == ( this->mId & 1 );
    }

private:
    long mId;
};

typedef std::list< Element > Elements;

int main( int argc, char * argv[] )
{
    std::string dummy;
    {
        Elements elements;

        std::cout << "Inserting "<< SIZE << " elements in container" << std::endl;
        std::cout << "Please wait..." << std::endl;

        /* inserting elements
         */
        for( long i=0; i<SIZE; ++i )
        {
            elements.push_back( i );
        }

        std::cout << "Size is " << elements.size() << std::endl;
        std::getline( std::cin, dummy); // waiting user press enter

        /* remove even elements
         */
        elements.remove_if( boost::bind( & Element::isEven, _1 ) );

        std::cout << "Size is " << elements.size() << std::endl;
        std::getline( std::cin, dummy);
    }

    std::getline( std::cin, dummy);

    return 0;
}

运行此代码会得到以下内存配置文件:

看起来 gcc 正在推迟释放,在我的测试程序中,最后它别无选择,在返回命令行之前释放内存。

为什么释放这么晚?

我已经尝试使用向量来测试另一个容器,并且缩小以适应的技巧起作用并在我期望的时候释放释放的内存。

gcc 4.5.0, linux 2.6.34

【问题讨论】:

  • 你是如何测量内存消耗的?

标签: c++ stl memory-management dealloc


【解决方案1】:

大多数操作系统(包括 Linux)只允许进程分配相当大的内存块,而不是非常小的内存块;即使有可能,进行许多小的分配也很可能比一些大的分配更昂贵。通常,C++ 库会从操作系统获取大块,并使用它自己的堆管理器将其中的小块分配给程序。大块一旦被这样划分,通常不会返回给操作系统;它们将继续分配给进程,并将在以后的分配中重复使用。

list 以小块(每个节点一个)分配内存,因此通常在程序退出之前不会释放分配的内存。 vector 可能直接从操作系统获取它的内存作为单个大分配,在这种情况下,它会在释放时释放。

【讨论】:

  • Linux 没有对分配的大小设置任何下限。另一方面,系统级别的分配可能相对昂贵,因此任何编写良好的运行时都会避免为小块这样做。
  • @JamesKanze:我可能是错的,但我认为您一次只能分配一个页面(通常为 8k)。无论如何,您是正确的,从系统中进行许多小分配会非常昂贵,因此即使可以,您也不会想要。
  • 在我的系统上,Vector 在退出范围时释放内存
  • @Nelstaar:是的,如果它的内存足够大以至于它的内存来自单个操作系统分配,那么内存将在那时被释放。如果您要创建数百万个非常小的向量,那么您可能会看到与 list 相同的模式。
  • 我还可以使用收缩适应 idom 释放一些内存:Elements(elements).swap(elements);
【解决方案2】:

您的图表究竟显示了什么? std::list的析构函数 释放所有内存,以便它可以在其他地方重用 程序,但释放不一定会将内存返回到 系统,它可以被其他进程使用。历史上,在 至少,在 Unix 下,一旦内存被分配给一个进程,它 保留在该进程中,直到进程终止。较新 算法可能能够真正将内存返回给操作系统,但即使 那么,碎片化之类的事情可能会阻止它这样做——如果 你分配,然后释放一个非常大的块,它可能会被返回,但是如果 你分配了很多小块(这是std::list 所做的), 运行时实际上会从操作系统分配大块,它 包裹出去;直到所有小块都无法返回如此大的块 它们中的内容已被释放,即使到那时也可能不会被归还。

【讨论】:

    【解决方案3】:

    这取决于您如何测量内存使用情况。如果它正在测量正在使用的进程内存,那么这正是您所期望的。

    程序请求内存并将其从控制环境(例如操作系统)分配给进程是很常见的,但是当内存被释放时,它不一定会从进程中被带走。它可能会返回到进程的空闲池中。

    这是过去的分配方式。对brksbrk 的调用将通过为进程提供更多内存来增加堆的大小。该内存将被添加到满足 malloc 调用的竞技场。

    但是,free 会将内存返回给竞技场,而不一定返回给操作系统。

    我想在这种情况下会发生类似的事情。

    【讨论】:

      【解决方案4】:

      您的内存配置文件实际上是进程的地址空间消耗(从进程本身的角度来看,mmap-ed 页面的总和,例如由/proc/self/statm/proc/self/maps 给出)。

      但是当一个 C 或 C++ 函数释放内存(之前分配有 mallocnew,它们正在使用 mmap 从 Linux 内核获取内存)使用 freedelete 时,它不是归还给系统(使用munmap - 因为这太慢或不切实际[碎片问题] - 但只是保留为将来可重复使用mallocnew

      因此,free 请求时确实发生了释放,但内存没有归还给系统,而是保留以供将来重复使用。

      如果你真的想要回馈内存,请编写自己的分配器(在mmapmunmap 上方),但通常不值得。

      如果您明确调用GC_gcollect()(但我不确定),也许使用Boehm's GC 可能会有所帮助(这非常有用,可以避免打扰free-ing 或delete -ing),但是你真的不应该那么在意。

      您的问题在技术上与gcc 无关(与另一个 C++ 编译器相同)。与Linux下的mallocnew(即标准C&C++库)相关。

      【讨论】:

        猜你喜欢
        • 2023-04-02
        • 1970-01-01
        • 2015-07-23
        • 2021-10-08
        • 2015-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-13
        相关资源
        最近更新 更多