realloc 和 memcpy 是如何工作的?

Posted

技术标签:

【中文标题】realloc 和 memcpy 是如何工作的?【英文标题】:How do realloc and memcpy work? 【发布时间】:2010-09-26 15:21:24 【问题描述】:

我有两个问题。

    realloc()memcpy() 是否以比迭代每个元素 O(N) 更快的方式将数组中的条目复制到另一个数组中?如果答案是肯定的,那么您认为它的复杂性是什么?

    如果分配的大小小于原始大小,realloc() 是将条目复制到其他地方还是在它们减小数组大小时将其保留?

【问题讨论】:

【参考方案1】:

1 - 不。他们一次复制一个块。请参阅 http://www.embedded.com/design/configurable-systems/4024961/Optimizing-Memcpy-improves-speed 以获得很好的分析。

2 - 这取决于实现。有关 glibc 的详细信息,请参阅http://www.gnu.org/software/libtool/manual/libc/Changing-Block-Size.html。 “在几种分配实现中,有时需要将块变小以复制它”

【讨论】:

谢谢。更新了链接。【参考方案2】:

让我们仔细看看memcpy,在我们讨论的时候,看看“大O”或朗道符号。

首先,大 O。正如我在其他地方谈到的,值得记住大 O 的定义,即某些函数 g(n) 被称为 O(f(n)) 当存在 g(n)kf(n) 的常数 k 时。常量的作用是让你忽略小细节,转而关注重要部分。正如每个人都注意到的那样,在大多数普通架构中,n 个字节的memcpy 将是 O(n),因为无论你必须移动那些 n 个字节,一次一个块。因此,可以在 C 中编写 memcpy 的第一个幼稚实现

unsigned char *
memcpy(unsigned char * s1, unsigned char * s2, long size)
    long ix;
    for(ix=0; ix < size; ix++)
        s1[ix] = s2[ix];
    return s1;

这实际上是 O(n),您可能想知道为什么我们还要为库例程而烦恼。然而,关于 libc 函数的事情是它们是编写特定于平台的实用程序的地方;如果您想针对架构进行优化,这是您可以做到的地方之一。所以,取决于架构,可能会有更高效的实现选项;例如,在 IBM 360 架构中,有一条MOVL 指令使用高度优化的微码移动大块数据。因此,代替该循环,memcpy 的 360 度实现可能看起来像

LR 3,S1      LOAD S1 ADDR in Register 3
LR 4,S2      
MOVL 3,4,SIZE

(顺便说一下,不能保证这是完全正确的 360 代码,但它可以用作说明。)这个实现 看起来 像而不是围绕像 C 代码那样循环,它只执行 3 条指令。

然而,真正发生的是,它在后台执行 O(n) 微指令。两者的不同是常数k;因为微码要快得多,并且因为指令上只有三个解码步骤,所以它大大比原始版本快,但仍然O(n) --只是常数变小了。

这就是为什么您可以充分利用 memcpy 的原因——它并不是渐进地更快,但实现速度与某人在特定架构上实现的速度一样快

【讨论】:

【参考方案3】:

与 realloc 相关的一些要点(检查 dev c++): void *realloc(void *ptr, size_t size);

    realloc() 函数应将 ptr 指向的内存对象的大小更改为 size 指定的大小。

    对象的内容应保持不变,直至新旧尺寸中的较小者。

    如果新大小较大,则对象新分配部分的内容未指定。

    如果 size 为 0 且 ptr 不是空指针,则释放指向的对象。

    如果 ptr 是空指针,realloc() 应等效于指定大小的 malloc()。

    如果 ptr 不匹配先前由 calloc()、malloc() 或 realloc() 返回的指针,或者如果空间先前已通过调用 free() 或 realloc() 释放,则行为未定义。

【讨论】:

【参考方案4】:

x86 具有用于扫描和匹配内存块中的字节/字的特殊指令,以及可用于复制内存块的特殊指令(毕竟它是 CISC cpu)。许多实现内联汇编语言的 C 编译器和用于内联整个函数的编译指示多年来一直在其库函数中利用这一点。

用于mem复制的是movsb/movsw结合rep指令。

CMPS/MOVS/SCAS/STOS
REP, REPE, REPNE, REPNZ, REPZ

使用 src/trg 地址和 int 计数设置寄存器,然后就可以了。

【讨论】:

【参考方案5】:

假设您正在谈论 glibc,并且由于您的问题取决于实现,因此最好只检查源代码:

malloc.c

memcpy.c

按照我的阅读方式,答案是:

    O(N) --- 无法在线性时间内复制项目。 使用 realloc() 缩小项目时,有时会复制大项目。

【讨论】:

【参考方案6】:
    可以推测,memcpy 可以这样编写,以便移动大量位。例如如果有利的话,完全可以使用 SSE 指令复制数据。

正如其他人所说,它不会比 O(n) 快,但内存系统通常有一个首选的块大小,并且可以一次写入缓存行的大小。

【讨论】:

【参考方案7】:

    memcpy 的性能确实不能比 O(N) 好,但可以对其进行优化,使其优于手动复制;例如,它可能能够在您复制 1 个字节的时间内复制 4 个字节。许多memcpy 实现是使用优化指令用汇编语言编写的,这些指令可以一次复制多个元素,这通常比一次复制一个字节的数据要快。

    我不太明白这个问题,如果你使用realloc来减小内存大小并且成功(返回非NULL),新位置将包含与旧位置相同的数据到新请求的大小。如果由于调用realloc(在减小大小时不常见)而更改了内存位置,则将复制内容,否则由于内存没有移动,因此无需进行复制。

【讨论】:

【参考方案8】:
    绝对没有办法比 O(N) 更快地复制 N 个项目。但是,它可能能够一次复制多个项目,或者使用特殊的处理器指令 - 所以它仍然可能比您自己做的要快。 我不确定,但我认为内存已完全重新分配。这是最安全的假设,无论如何它可能取决于实现。

【讨论】:

以上是关于realloc 和 memcpy 是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

realloc 如何知道要复制多少?

关于new操作符如何实现C中的realloc函数

`realloc` 如何在后台实际工作?

关于memalign和malloc的区别

memcpy 的内部实现是如何工作的?

有关C语言内存管理的一些总结