【问题标题】:realloc but only first few bytes is meaningfulrealloc 但只有前几个字节有意义
【发布时间】:2012-10-26 04:46:30
【问题描述】:

假设我使用ptr = malloc(old_size); 分配了一个带有old_size 字节的内存块。只有第一个 header_size 字节有意义。我要把大小增加到new_size

new_size 大于old_size 并且old_size 大于header_size

之前:

/- - - - - - - old_size - - - - - - - \
+===============+---------------------+
 \-header_size-/

之后:

/- - - - - - - - - - - - - - - new_size - - - - - - - - - - - - - - - - - - -\
+===============+------------------------------------------------------------+
\- header_size-/

我不在乎ptr + header_size 之后存储的内容,因为我会向那里读取一些数据。

方法一:直接去new_size

ptr = realloc(ptr, new_size);

方法2:缩小到header_size并增长到new_size

ptr = realloc(ptr, header_size);
ptr = realloc(ptr, new_size);

方法3:分配一个新的内存块,复制第一个header_size字节

void *newptr = malloc(new_size);
memcpy(newptr, ptr, header_size);
free(ptr);
ptr = newptr;

哪个更快?

【问题讨论】:

  • @nos 我会在重新分配后写一些超出header_size 的数据,所以我不在乎里面有什么。

标签: c memory-management malloc realloc libc


【解决方案1】:

malloc(对于整个块)和realloc(对于增加大小时超出旧块大小的空间)都不能保证您收到的内存将包含什么,如果您想要设置这些多余的字节为零(例如),你必须自己做这样的事情:

// ptr contains current block.
void *saveptr = ptr;
ptr = realloc (ptr, new_size);
if (ptr == NULL) {
    // do something intelligent like recover saveptr and exit.
}
memset (ptr + header_size, 0, new_size - header_size);

但是,由于您已声明不关心标头之外的内容,因此几乎可以肯定最快的是单个 realloc,因为这可能会在幕后进行优化。

调用它两次进行收缩和扩展,或者调用 malloc-new/memcpy/free-old 不太可能像所有优化一样有效,您应该衡量,不要猜测!

请记住,realloc 不一定要复制您的记忆。如果扩展可以就地完成,那么智能堆管理器只会增加块的大小而不复制任何内容,例如:

+-----------+   ^        +-----------+ <- At same address,
| Old block |   | Need   | New block |      no copying
|           |   | this   |           |      involved.
+-----------+   | much   |           |
| Free      |   | now.   |           |
|           |   v        +-----------+
|           |            | Free      |
|           |            |           |
+-----------+            +-----------+

【讨论】:

  • 嗯?当然realloc() 保证新内存将包含与旧块一样多的内容。
  • @unwind:我的意思是超出旧边界,而不是旧块中的内存。我会澄清的。
  • @lqs,因为你在评论中说你不在乎非标题包含什么,我已经简化了我的答案以适应。
【解决方案2】:

它几乎肯定取决于old_sizenew_sizeheader_size 的值,也取决于实现。您必须选择一些值并进行衡量。

1) 在header_size == old_size-1 &amp;&amp; old_size == new_size-1 的情况下可能是最好的,因为它为您提供了单个realloc 基本上是空操作的最佳机会。 (2) 在这种情况下应该只会稍微慢一点(2 个几乎无操作比 1 稍慢)。

3) 在header_size == 1 &amp;&amp; old_size == 1024*1024 &amp;&amp; new_size == 2048*1024 的情况下可能是最好的,因为realloc 将不得不移动分配,但您避免复制您不关心的1MB 数据。 (2) 在这种情况下应该只会稍微慢一点。

2) 当header_size 远小于old_size 时可能是最好的,并且new_sizerealloc 很可能会重新定位的范围内,但合理可能不会。那么您无法预测(1)和(3)中的哪一个会比(2)快得多。

在分析 (2) 时,我假设 realloc 向下大约是空闲的并返回相同的指针。这不能保证。我能想到两件事会让你感到困惑:

  • realloc 向下复制到新的分配
  • realloc 向下拆分缓冲区以创建一个新的空闲内存块,但是当您再次重新分配时,分配器不会将该新的空闲块再次按顺序重新合并回您的缓冲区无需复制即可返回。

其中任何一个都可能使 (2) 比 (1) 贵得多。因此,在 (1) 的优势(有时避免复制任何东西)和 (3) 的优势(有时避免复制太多)之间对冲您的赌注,这是一个实现细节(2)。

顺便说一句,这种关于性能的闲散猜测更有效地暂时解释您的观察结果,而不是暂时预测我们在不太可能发生的情况下会做出什么观察结果,因为我们实际上足够关心性能来测试它。

此外,我怀疑对于大型分配,通过将内存重新映射到新地址,实现甚至可以在不复制任何内容的情况下重新定位 realloc。在这种情况下,它们都会很快。不过,我还没有研究实现是否真的这样做。

【讨论】:

    【解决方案3】:

    这可能取决于大小以及是否需要复制。

    方法 1 将复制旧块中包含的所有内容 - 但如果您不经常这样做,您将不会注意到。

    方法 2 只会复制您需要保留的内容,因为您事先丢弃了其他所有内容。

    方法3将无条件复制,而其他方法仅在内存块无法调整大小时复制。

    就个人而言,如果你经常这样做,我更喜欢方法 2,如果你很少这样做,我更喜欢方法 1。分别,我会介绍其中哪些会更快。

    【讨论】:

    • 方法 2 是唯一一种 both 完全避免复制的方法,如果块后有空闲空间,and 否则只复制最小值记忆。方法 1 在最好的情况下是最有效的,但在最坏的情况下也可能是效率最低的。这确实取决于我们现在谈论的大量数据是否值得额外的函数开销。
    • @ams 实际上是否有realloc 实现在即将被realloc'ed 的块之后考虑空闲块?在我看来这不太可能。
    • @pmr:即使他们不尝试合并块,在new_size - old_size很小的情况下,块的末尾可能有空闲空间可以用来满足新的大小,由于 old_size 已被四舍五入。
    • @pmr 为什么不将分配扩展到以下可用空间,如果有的话?当然,必须有人以这种方式实现它,但是那里有很多聪明人!但实际上,您甚至不能假设 减少 分配的大小不会产生副本。
    • @pmr 至少malloc 的一些实现使用mmap 用于大于一页(通常为4KiB)的分配,因此扩展分配的大小几乎是无操作的。这取决于地址空间的拥挤程度,但在 64 位机器上这可能不是问题。
    猜你喜欢
    • 2014-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-16
    • 2017-10-17
    • 1970-01-01
    相关资源
    最近更新 更多