在程序变得低效之前,允许多少新的[]和删除[]分配是否有限制?



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

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

struct client {
char name[80];
char address[80];
char phonenumber[80];
};

正如您所看到的,这个结构体的大小是240字节。因此,20万个客户端将占用48MB的内存。显然,这种结构的优点是易于管理,并为回收客户创建了一个"免费列表"。然而,如果明天我需要加载5M客户端,那么这将增长到1.2Gb的RAM。

现在,很明显,在大多数情况下,客户端的名称、地址和电话号码占用的字节要少得多,所以我没有使用上面的结构,而是考虑使用以下结构:

struct client {
char *name;
char *address;
char *phonenumber;
};

然后让*名称、*地址和*电话号码指向按存储每个信息所需的确切大小动态分配的结构。

然而,我确实怀疑,随着越来越多的客户端以这种方式加载,这将大大增加所需的新[]和删除[]分配的数量,我的问题是,这是否会在某个时候影响性能,例如,如果我想突然删除50万个1M客户端,并用35万个不同的客户端替换它们?

我怀疑,在我分配了1M"可变长度"的小缓冲区后,如果我"删除"了其中的许多缓冲区,然后想创建新的分配来回收被删除的缓冲区,这不会给分配器找到它们带来一些开销吗?

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

在现代堆实现中,当堆分配"太多"时,你的程序可能不会"碰壁"并陷入停顿(当然,除非你的计算机实际上耗尽了物理RAM),但它会比不需要那么多的可比程序消耗更多的RAM和CPU周期。

考虑到这一点,使用无数微小的内存分配可能不是最好的方法。除了效率不太理想(因为每一个微小的分配都需要一个单独的记账字节块来跟踪),许多微小的分配还会导致内存碎片问题(在具有虚拟内存的现代64位系统上,这不是一个问题,但仍然需要考虑),而且很难正确管理(如果手动分配,很容易导致内存泄漏或两次释放)。

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

有多少新的[]&是否允许在程序变得低效之前删除[]分配?

与不进行分配的程序相比,即使是一次分配也会降低程序的时间效率,假设不需要进行分配。低效率(至少)与分配数量成线性关系(取决于分配函数的实现)。

程序何时有效和何时无效没有客观限制。如果你正在编写一个有严格实时要求的程序,那么你的程序效率太低时有一个限制,但对于其他程序——也就是大多数程序,程序效率太低时也没有客观限制。一般来说,如果您的程序执行时间过长,那么用户可能会认为它效率低下。"太长"对于使用该程序的人来说是主观的。

比您建议的更好的解决方案是使用std::string成员。现在,它的大小可能是指针大小的倍数(~4取决于实现),但(假设实现良好)它很神奇,当字符串适合该空间时,它可以避免动态分配。与每个单独分配相比,这节省了大量时间,与就地阵列相比,这也节省了大量空间。更重要的是,它不需要容易出错的手动内存管理。

存储客户端列表的最佳内存效率方法是使用单个庞大的char数组,其中每个字符串都是连续存储的。您可以使用指向字符串的指针来表示客户端的开始。如果你不想对特定的成员进行线性搜索,那么你可以像问题中那样使用指针类,但要指向这个单独的数组,而不是单独的分配。

最新更新