memcpy 的内部实现是如何工作的?
Posted
技术标签:
【中文标题】memcpy 的内部实现是如何工作的?【英文标题】:How does the internal implementation of memcpy work? 【发布时间】:2013-07-04 03:19:57 【问题描述】:标准 C 函数“memcpy”如何工作?它必须将(大)块 RAM 复制到 RAM 中的另一个区域。因为我知道你不能在汇编中直接从 RAM 移动到 RAM(使用 mov 指令)所以我猜它在复制时使用 CPU 寄存器作为中间存储器?
但它是如何复制的?按块(如何按块复制?),按单个字节 (char) 或它们拥有的最大数据类型(以 long long double 复制 - 在我的系统上是 12 个字节)。
编辑:好吧,显然你可以直接将数据从 RAM 移动到 RAM,我不是装配专家,我所了解的关于装配的所有知识都来自该文档 (X86 assembly guide),该文档在关于不能从 RAM 移动到 RAM 的 mov 指令的部分。显然这不是真的。
【问题讨论】:
这是特定于平台的。请指定一个平台。 我使用 linux、mac 和 windows(分别为 32 位、64 位和 32 位),但我在使用 Linux 时问了这个问题。 【参考方案1】:视情况而定。通常,您无法在单个周期中物理复制大于最大可用寄存器的任何内容,但这并不是当今机器的真正工作方式。在实践中,您真正关心的不是 CPU 在做什么,而是更关心 DRAM 的特性。机器的内存层次结构将在以尽可能快的方式执行此复制方面发挥至关重要的决定作用(例如,您是否加载了整个缓存行?相对于复制操作而言,DRAM 行的大小是多少?)。一个实现可能会选择使用某种向量指令来实现memcpy
。在不参考具体实现的情况下,它实际上是一个字节对字节的副本,带有一个位置的缓冲区。
Here's a fun article 描述了一个人对优化memcpy
的冒险。主要的要点是,它始终会根据您可以廉价执行的指令针对特定的架构和环境。
【讨论】:
【参考方案2】:memcpy
的一个简单实现是:
while (n--) *s2++ = *s1++;
但是glibc
通常在汇编代码中使用一些巧妙的实现。 memcpy
调用通常是内联的。
在 x86 上,代码检查 size 参数是 2
的倍数还是 4
的倍数(使用 gcc
内置函数)并使用带有 movl
指令的循环(复制 4
bytes) 否则调用一般情况。
一般情况使用使用rep
和movsl
指令的快速块复制程序集。
【讨论】:
但是 s2 和 s1 是 void 指针,我认为你不能取消引用 void 指针。 @ouah - 为什么只在大小为 4 的倍数时使用movl
,而不总是尝试使用movl
?如果您必须复制总共 50 个字节,您不能使用 12 movl
和 2 mov
复制吗?
@Rockstar5645 - 你必须先投,我相信 ouah 引用了这个:gcc memcpy implementation【参考方案3】:
memcpy
的实现高度特定于实现它的系统。实现通常由硬件辅助。
Memory-to-memory mov 指令并不少见——它们至少从PDP-11
次就已经存在,那时您可以编写如下内容:
MOV FROM, R2
MOV TO, R3
MOV R2, R4
ADD LEN, R4
CP: MOV (R2+), (R3+) ; "(Rx+)" means "*Rx++" in C
CMP R2, R4
BNE CP
注释行大致相当于C的
*to++ = *from++;
当代 CPU 具有直接实现 memcpy
的指令:您使用源地址和目标地址加载特殊寄存器,调用内存复制命令,然后让 CPU 完成其余工作。
【讨论】:
“它们至少从 PDP-11 开始就已经存在了”——要长得多。 @JimBalter 这一点都不让我吃惊:) to 和 from 是 void 指针,我认为你不能取消引用 void 指针。你先把它们输入(unsigned char*)
@Rockstar5645 程序集没有类型的概念,因此很高兴取消引用您作为void*
传递的任何地址。当然,如果您使用 C 编写实现,则必须将这些指针类型转换为可以取消引用的内容,例如 unsigned char*
。以上是关于memcpy 的内部实现是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript工作机制:V8 引擎内部机制及如何编写优化代码的5个诀窍