汇编指令把数据从内存一块区域复制到另外一块区域
最近再次阅读 CSAPP 里面的有关汇编的一节, 它说到了 mov
指令, mov
可以把常量, 寄存器的值, 内存的值移动(move)到寄存器, 内存. 但是不能直接把某个地址内存的值移动到另外一个内存地址. 为什么会有这个限制?
如果考虑到当前的值要做计算, 那么这个值从内存加载到CPU, 然后做计算, 之后再保存到内存. 看上去没啥性能损失, 但是假如只是把某个内存的数据复制(移动)到另外一个内存地址, 那么这个性能损失就很大.
为什么会这么想, 这是因为在
可以看到从内存加载, 然后再保存到内存需要的相对时间确实很长, 如果有一个方案直接告诉内存控制器, 把某地址开始的多少数据直接复制到另外一块区域, 那将是飞速提升, 因为只要CPU发出一个内存控制器的指令就可以. 但是这看上去很美, 因为内存控制器并不知道进程虚拟内存这些东西, 它只知道硬件物理内存. 虚拟内存要写的2个连续字节可能在物理内存上是相隔很远的两块地方.
如果一个内存数据并不需要做算术逻辑运算, 那么可以使用专门的字符处理指令 movs
, 它有对应不同宽度的: movsb
, movsw
, movsd
, movsq
. 它们的使用会涉及到 RSI, RDI, RCX 寄存器, 以及 DF flag, 并且结合 REP
指令来实现连续复制.
但是这里我更关心的是, 这些被移动的字节是不是要到CPU绕一圈? 也就是说这些字节是不是需要占用系统总线(system bus). 根据我互联网上查到的数据, 这些字节通常情况下都需要到 CPU 绕一圈, 涉及到加载(load)和保存(save). 也就是避免不了性能的延迟. 但是如果要移动的数据已经在缓存 (L1, L2, L3), 那么将不需要走系统总线加载. 同时这些加载可能都是成块加载, 所以通常连续的内存区域都会同时被一次加载, 提高了性能. 另外从缓存刷新到内存也是成块(页)刷新到, 那么它也会提高性能.
go语言把部分CPU从 AVX 迁移到 REP movsb - https://github.com/golang/go/issues/66958
有关 memcpy
Linux内核中的memcpy函数是一个用于内存复制的函数,它的主要功能是将一块内存中的数据原封不动地复制到另一块内存中。这个函数在内核编程中非常重要,因为它直接操作内存,效率要求非常高。
声明在 string.h 中void *memcpy(void *dest, const void *src, size_t n);
为了提高效率,Linux内核中的memcpy通常实现为汇编语言,特别是对于小数据块的复制,会有特别的优化。对于更大的数据块,它可能会利用CPU的特定指令(如SSE/AVX指令集)来提高复制速度。
memcpy 可以根据CPU 的指令集选用 movs
系列的指令, 也可以选择 AVX 或 SIMD 来提高在该架构上的性能.
summary
从内存到内存的复制, 还是需要占用系统总线的.