在程序变得低效之前,允许多少 new[] 和 delete[] 分配有限制吗?

Mik*_*ich -2 c++ memory

我不确定以前是否有人问过这个问题,所以我会试一试。

我有用于加载大客户列表(20 万个客户)的代码。每个客户端都存储在一个(当前)固定大小的结构中,其中包含他的姓名、地址和电话号码,如下所示:

struct client {
    char name[80];
    char address[80];
    char phonenumber[80];
};
Run Code Online (Sandbox Code Playgroud)

如您所见,该结构的大小为 240 字节。因此,200k 客户端将占用 48MB 内存。显然,这种结构的优点是易于管理并为回收客户创建“免费列表”。但是,如果明天我需要加载 5M 客户端,那么这将增长到 1.2Gb 的 RAM。

现在,显然在大多数情况下,客户的姓名、地址和电话号码占用的空间远少于 80 个字节,因此我想到使用如下结构代替上述结构:

struct client {
    char *name;
    char *address;
    char *phonenumber;
};
Run Code Online (Sandbox Code Playgroud)

然后让 *name、*address 和 *phonenumber 指向以确切所需大小动态分配的结构,用于存储每个信息。

然而,我确实怀疑,随着更多客户端以这种方式加载,它会大大增加所需的 new[] 和 delete[] 分配的数量,我的问题是这是否会在某些时候损害性能,例如,如果我想突然删除 1M 客户端中的 500k 并用 350k 不同客户端替换它们?

我怀疑在我分配了 1M 个“可变长度”小缓冲区之后,如果我“删除”其中的许多缓冲区,然后想要创建新的分配来回收已删除的那些,是否会导致分配器的一些开销找到他们?

Jer*_*ner 6

答案是进行许多小的动态分配和解除分配需要一些开销(在每个分配的 CPU 周期和每个分配的簿记内存方面)。多少开销将在很大程度上取决于您的运行时内存堆是如何实现的;然而,大多数现代/流行的运行时都有经过优化的堆实现,效率很高。有一些 关于如何实现各种操作系统的堆的文章,您可以阅读这些文章以了解它们的工作原理。

在现代堆实现中,当堆分配“太多”时,您的程序可能不会“撞墙”并停止运行(当然,除非您的计算机实际上用完了物理 RAM),但它会使用与不需要这么多的类似程序相比,按比例增加更多的 RAM 和 CPU 周期。

鉴于此,使用无数微小的内存分配可能不是最好的方法。除了效率不高(因为每一个微小的分配都需要一个单独的簿记字节块来跟踪),许多微小的分配可能会导致内存碎片问题(这在具有虚拟内存的现代 64 位系统,但仍然需要考虑),以及难以正确管理(如果您手动进行分配,很容易导致内存泄漏或双重释放)。

正如其他人在评论中所建议的那样,在 C++ 中不鼓励显式调用newdelete;它几乎总是更好地使用更高级别的数据结构(例如std::stringstd::mapstd::vector等,甚至是正确的数据库层代替),因为做这样的说法很多困难的设计工作,会为你已经完成,节约了您的不得不重新发现和重新解决其他人过去已经处理过的所有问题的痛苦。例如,std::string已经实现了短字符串优化允许存储短于特定字节数的字符串,而无需单独的堆分配;类似于您在自己的设计中尝试进行的权衡,但您可以在适当的时候“免费”获得优化,只需使用std::string存储字符串数据即可。