C++ new 分配的 space 多于预期

C++ new allocates more space than expected

我正在尝试测试一些 c++ 应用程序在内存要求很高时的行为,但似乎我无法使用所有可用的 ram。我有以下程序:

class Node {
    public:
        Node *next;
};


int main() {
    int i=0;

    Node *first = new Node();
    Node *last = first;

    //Should be   120000000 * 8 bytes each -> approx 1 GB
    for (i=0; i < 120000000; i++) {
        Node *node = new Node();
        node->next = 0;
        last->next = node;
        last = last->next;
    }


    for (i=0; i < 120000000; i++) {
        Node *oldfirst = first;
        first = first->next;
        delete oldfirst;
    }

    delete first;

    return 0;    
}

应该分配大约 1 GB 的数据,因为节点 class 占用 8 个字节。我已经通过 sizeof、gdb,甚至 valgrind 验证了这一点。

但是这个程序分配了大约 4 GB 的数据!如果我将这个大小加倍( 120000000 -> 2400000000 ),那么有 2 个选项(我的笔记本电脑安装了 8GB RAM):

关键是我无法测试分配 2 GB 数据的应用程序,因为它消耗 8 GB 的 RAM!

我想可能是我请求一个新的Node时分配的字节数大于8(也就是Node对象的大小),所以我尝试了以下方法:

class Node {
    public:
        Node *next;
        Node *second_next;
};


int main() {
    int i=0;

    Node *first = new Node();
    Node *last = first;

    //Should be   120000000 * 8 bytes each -> approx 1 GB
    for (i=0; i < 120000000; i++) {
        Node *node = new Node();
        node->next = 0;
        last->next = node;
        last = last->next;
    }


    for (i=0; i < 120000000; i++) {
        Node *oldfirst = first;
        first = first->next;
        delete oldfirst;
    }

    delete first;

    return 0;    
}

现在Node对象占用16个字节。应用程序的内存占用完全一样! 120000000 导致使用 4 GB RAM,240000000 导致我的应用程序被 Linux 内核杀死。

所以我遇到了this post

C++中每个new都至少分配32个字节是真的吗?

简短回答 - 您忘记考虑内存分配开销。内存分配器本身需要跟踪分配的内存块,这些内存块本身会消耗内存,如果您分配很多小块,与您请求的内存量相比,开销会大得不合理。然后还要考虑块对齐,许多分配器都试图变得聪明并对齐内存块以获得最佳 CPU 访问速度,因此它们将与缓存行对齐。

最后但并非最不重要的一点是,给您 8 字节内存的成功请求很可能在幕后分配了更大的块。毕竟,向 malloc/new 请求特定数量的内存只能保证您将获得至少那个大小的块,而不是那个大小。

对于分配大量小块的用例,您需要研究像池分配器这样的东西,它可以最大限度地减少开销。

实际上,您可能应该考虑的是一种比具有许多小节点的非常大的链表更好的数据结构。

如果您只是想了解您正在使用的分配,它有 8 字节的开销,最小 32 字节包括开销和 16 字节对齐。例如:

1 .. 24 字节:占用 32
25 .. 40 字节:占用 48
41 .. 56 字节:需要 64
等等

如果你想有效地使用大量的微小对象,你需要以其他方式分配它们(批量分配,然后自己细分分配)。

这实际上取决于 malloc 实施。我在我的机器(64 位)上测试过,如果我使用 tcmalloc,它大约需要 1GB 内存。在内部 tcmalloc 为不同的分配大小保留单独的池,因此对于每个池,不需要记录对象大小,这减少了小对象的开销。对于8字节分配,根本没有开销。

hidden $ cat c.cpp 
#include <iostream>
#include <string>
using namespace std;

struct Node { Node *next; };

int main() {
    Node *first = new Node();
    Node *last = first;
    for (int i=0; i < 120000000; i++) {
        Node *node = new Node();
        node->next = 0;
        last = last->next = node;
    }

    cout << "Press <Enter> to continue...";
    string s;
    cin >> s;
    return 0;
}
hidden $ g++ -std=c++11 -O3 c.cpp /usr/lib/libtcmalloc_minimal.so.4
hidden $ ./a.out & { sleep 5; ps -C a.out -o rss; killall a.out; }
[1] 31500
Press <Enter> to continue...
[1]+  Stopped                 ./a.out
  RSS
947064