简体   繁体   English

Memory 没有释放 std::list <std::shared_ptr<std::string> &gt; C++ </std::shared_ptr<std::string>

[英]Memory not freeing up std::list<std::shared_ptr<std::string>> C++

I'm populating a list of string shared pointers.我正在填充字符串共享指针列表。 At some point in my program, I clear the list.在我的程序中的某个时刻,我清除了列表。 But the memory consumption of my program does not reduce even after I call clear() function of list.但是即使在我调用列表的clear() function 之后,我的程序的 memory 消耗也没有减少。 Any Idea why?任何想法为什么?

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

int main()
{
    std::string text = "                                                            \
        PREFACE                                                                     \
                                                                                    \
        Most of the adventures recorded in this book really occurred; one or two    \
        were experiences of my own, the rest those of boys who were schoolmates     \
        of mine. Huck Finn is drawn from life; Tom Sawyer also, but not from an     \
        individual--he is a combination of the characteristics of three boys whom   \
        I knew, and therefore belongs to the composite order of architecture.       \
                                                                                    \
        The odd superstitions touched upon were all prevalent among children and    \
        slaves in the West at the period of this story--that is to say, thirty or   \
        forty years ago.                                                            \
                                                                                    \
        Although my book is intended mainly for the entertainment of boys and       \
        girls, I hope it will not be shunned by men and women on that account,      \
        for part of my plan has been to try to pleasantly remind adults of what     \
        they once were themselves, and of how they felt and thought and talked,     \
        and what queer enterprises they sometimes engaged in.                       \
    ";

    std::list<std::shared_ptr<std::string>> data;
    for (auto i = 0u; i < 999999; ++i) {
        data.push_back(std::make_shared<std::string>(text));
    }

    std::cout << "Data loaded. Press any key to continue...";
    std::cin.get();

    data.clear(); // memory does not reduce
    std::cout << "Data unloaded. Press any key to continue...";
    std::cin.get();

    std::cout << "Container size:" << data.size() << std::endl;
    std::cout << "Press any key to exit...";
    std::cin.get();

    return 0;
}

I'm debugging on WSL.我正在调试 WSL。 I used both linux top (on WSL) and Windows Task Manager to check memory usage at each stop.我使用了 linux 顶部(在 WSL 上)和 Windows 任务管理器来检查 memory 在每个站点的使用情况。 But valgrind does not report any memory leaks.但是 valgrind 没有报告任何 memory 泄漏。

PS Please don't ask why I'm using shared pointers. PS请不要问我为什么要使用共享指针。 Proposing different approach would be useful but the main purpose of this question is to understand this behavior.提出不同的方法会很有用,但这个问题的主要目的是理解这种行为。 Since even cppref doesn't explain this.因为即使 cppref 也没有解释这一点。 Appreciate if someone can explain this behavior rather than fixing it.欣赏是否有人可以解释这种行为而不是修复它。

I don't need a fix.我不需要修复。 I need an explanation.我需要一个解释。

I used both linux top (on WSL) and Windows Task Manager to check memory usage at each stop.我使用了 linux 顶部(在 WSL 上)和 Windows 任务管理器来检查 memory 在每个站点的使用情况。 But valgrind does not report any memory leaks.但是 valgrind 没有报告任何 memory 泄漏。

TLDR : Allocating memory is an expensive operation relative to other code paths. TLDR :相对于其他代码路径,分配 memory 是一项昂贵的操作。 To improve performance, almost all heap managers will continue to hold onto the memory for subsequent allocations instead of giving it directly back from where it came from.为了提高性能,几乎所有堆管理器都将继续保留 memory 以进行后续分配,而不是直接从其来源处返回。

When memory is allocated by your program code, it goes through a tier of memory heaps to grant that allocation.当您的程序代码分配 memory 时,它会通过一层 memory 堆来授予该分配。 When your program invokes "new" (via make_shared ), it calls into the C/C++ runtime to allocate memory.当您的程序调用“new”(通过make_shared )时,它会调用 C/C++ 运行时来分配 memory。 The C/C++ runtime, if it doesn't have a sufficiently large enough contiguous byte range in its heap to grant that allocation, it will thunk down to the process heap via OS specific library calls to ask for more memory. C/C++ 运行时,如果它的堆中没有足够大的连续字节范围来授予该分配,它将通过 OS 特定的库调用请求更多 memory 到进程堆。 The process heap, if it doesn't have enough to allocate right away, it makes a system call to allocate more virtual memory... and probably a few more heaps as well.进程堆,如果它没有足够的空间来立即分配,它会进行系统调用来分配更多的虚拟 memory ......并且可能还有更多的堆。 And let's not forget that memory likely has to be paged in, but I digress.我们不要忘记 memory 可能必须被分页,但我离题了。

Each one of these heap accesses requires taking a lock on a data structure to manage the heap allocation being requested, and possibly a system call to the OS.这些堆访问中的每一个都需要锁定数据结构以管理所请求的堆分配,并且可能需要对操作系统进行系统调用。 And possibly some extra effort to collapse or re-arrange blocks of memory as needed.并且可能需要一些额外的努力来折叠或重新排列 memory 的块。 That's why memory heaps are tiered.这就是 memory 堆分层的原因。 If every new and delete call were to go directly to the virtual memory manager, program and system performance would be really slow from the sheer number of system calls to do this.如果每个新的和删除的调用都是对 go 直接对虚拟 memory 管理器进行调用,那么由于执行此操作的系统调用的绝对数量,程序和系统性能将非常缓慢。

Similarly releasing memory back to where it came from is also a similar performance hit.同样,将 memory 释放回原来的位置也是类似的性能损失。 It's possible that these heaps will release back to its parent heap when it wants to compact, but don't expect it to do that from a single invocation of "delete".当它想要压缩时,这些堆可能会释放回其父堆,但不要指望它会通过一次“删除”调用来做到这一点。

Tools like Top and Task Manager can only observe the amount of virtual memory allocated by the process from the OS. Top和任务管理器等工具只能从操作系统观察进程分配的虚拟memory的数量。 They aren't aware of free'd allocations being managed by the runtime libraries of your program.他们不知道由程序的运行时库管理的免费分配。 Whereas Valgrind instruments itself into your code and can hook itself closer to the C++ memory manager.而 Valgrind 将自己插入到您的代码中,并且可以更接近 C++ memory 管理器。 But even Valgrind will report false positives every now and then.但即使是 Valgrind 也会时不时地报告误报。

As Nicolai Josuttis mentioned in this talk , make_shared does not call new twice, instead it allocates memory just one both for control block and for the resource you are pointing to, and so it won't deallocate the block of the memory until all the shared pointers and weak pointers that are referencing to that control block are deleted正如 Nicolai Josuttis 在本次演讲中提到的, make_shared不会调用new两次,而是将 memory 分配给控制块和您指向的资源,因此它不会释放 memory 的块,直到所有共享引用该控制块的指针和弱指针被删除

however in this case no more shared/weak pointers are pointing to those control blocks, infact, if instead of using std::string you use a your object, you will see the destructors been called ad the clear() call:但是在这种情况下,没有更多的共享/弱指针指向这些控制块,事实上,如果您使用 object 而不是使用std::string ,您将看到析构函数被调用和clear()调用:

#include <memory>
#include <string>
#include <list>
#include <iostream>
class A{
public:
    ~A(){std::cout<<"destructor"<<std::endl;}
};
int main()
{

    std::list<std::shared_ptr<A>> data;
    for (auto i = 0u; i < 10; ++i) {
        data.push_back(std::make_shared<A>());
    }

    std::cout << "Data loaded. Press any key to continue...";
    std::cin.get();

    data.clear(); // memory does not reduce
    std::cout << "Data unloaded. Press any key to continue...";
    std::cin.get();

    std::cout << "Container size:" << data.size() << std::endl;
    std::cout << "Press any key to exit...";
    std::cin.get();

    return 0;
}

OUTPUT: OUTPUT:

Data loaded. Press any key to continue...
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
destructor
Data unloaded. Press any key to continue...

So you can't see the memory decreasing immediately because the allocator that you are using is optimizing the memory, and so freeing memory will only means "free to overwrite this piece of memory" and not giving it back to the OS, in order to use that in future allocation, without involving every time the OS因此,您看不到 memory 立即减少,因为您使用的分配器正在优化 memory,因此freeing memory 仅意味着“免费覆盖这块内存”并且不意味着“免费覆盖”在将来的分配中使用它,而不涉及每次操作系统

default use tcmalloc, you can use jemalloc with "export LD_PRELOAD=/usr/lib64/libjemalloc.so.1".默认使用 tcmalloc,您可以将 jemalloc 与“export LD_PRELOAD=/usr/lib64/libjemalloc.so.1”一起使用。 The tcmalloc allocator optimized the memory used, it suppose the prog will use memory next time for lots of min memory, if you use vector( continuous memory), the tcmalloc will free it after destruct it. tcmalloc 分配器优化了使用的 memory,它假设 prog 下一次将使用 memory 很多分钟 memory,如果你将使用向量(在破坏内存后释放它)它。 or you can add some code after list used like:或者您可以在使用的列表之后添加一些代码,例如:

std::vector<std::string> myVec;
for(int i = 0; i < 1000; ++i)
{
    myVec.push_back(string(256, 'a'));
}
myVec.clear();

Then you'll find: the list memory is freed!然后你会发现:列表 memory 被释放了!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM