C++new分配的空间比预期的要多

C++ new allocates more space than expected

本文关键字:分配 空间 C++new      更新时间:2023-10-16

当内存需求很高时,我试图测试一些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;    
}

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

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

  • 如果我关闭了交换区域,进程就会被内核杀死
  • 如果没有,那么分页就会发生,操作系统就会变得非常慢

重点是,我无法测试一个分配2GB数据的应用程序,因为它消耗8GB的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导致使用了4GB的RAM,240000000导致我的应用程序被Linux内核杀死。

所以我偶然发现了这个后

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