为什么正向重叠内存的memmove使用rep movsb时速度慢,且libc仍采用该指令?
为什么正向重叠内存的memmove使用rep movsb时速度慢,且libc仍采用该指令?
咱们先把问题和测试场景摆出来:你做了个基准测试,发现用rep movsb实现的memmove,在处理8193字节的重叠数组时,比不使用rep movsb处理8191字节的memmove慢好几个数量级。想知道这背后的原因,还有为啥libc明明有这个问题还坚持用rep movsb?
先看你用到的基准测试代码(补全了截断的部分):
#include <benchmark/benchmark.h> #include <cstring> using namespace std; alignas(4096) unsigned char v[1024 * 1024]; void bm_memmove(benchmark::State& state) { // 测试逻辑:将v+1位置的内存,正向拷贝到v位置,拷贝长度为传入的参数 for (auto _ : state) { memmove(v, v + 1, state.range(0)); benchmark::DoNotOptimize(v); } } // 注册两个测试用例:8191字节和8193字节 BENCHMARK(bm_memmove)->Arg(8191)->Arg(8193); BENCHMARK_MAIN();
为啥8193字节用rep movsb会慢?
核心原因在于CPU缓存机制和rep movsb的优化场景不匹配:
- 多数libc的
memmove实现会根据拷贝长度选分支:短长度(比如小于8192字节)用普通的寄存器循环拷贝,长长度则切换到rep movsb。8193刚好超过了这个阈值,触发了rep movsb分支。 rep movsb是为无冲突的连续批量拷贝优化的,现代CPU有专门的快速字符串硬件支持它,能高效预取内存、流水线并行处理。但在正向重叠拷贝场景下,每拷贝一个字节,源地址就会覆盖刚写入的目的地址附近的内存,这会导致频繁的缓存行失效:刚把数据写到缓存行,下一刻又要读取相邻的位置,缓存还没完成同步就被标记为脏,需要频繁回写到内存,彻底打乱了rep movsb的预取和流水线优化,反而变成了低效的单字节拷贝。- 而8191字节时用的普通循环拷贝,是用寄存器批量处理(比如一次拷8/16字节),libc的实现会针对重叠场景做优化,调整拷贝块的大小,减少缓存冲突的影响,所以速度反而快得多。
为啥libc还坚持用rep movsb?
这是通用场景性能和极端场景的权衡结果:
rep movsb在绝大多数场景下是更快的:比如非重叠的大内存拷贝,这是最常见的使用场景,它能充分利用CPU的带宽和硬件优化,比普通循环快很多。- 正向重叠的大内存拷贝属于极端场景,实际业务中很少出现。libc作为通用库,必须优先保证大多数用户的常用场景性能最优,而不是为了小众场景牺牲通用性能。
- 另外,有些libc实现会在检测到重叠且长度较大时,自动切换到反向拷贝(从后往前拷),避免缓存冲突,但正向拷贝的分支依然保留
rep movsb,因为反向拷贝也有额外的地址计算开销,对于非重叠场景反而不划算。
内容来源于stack exchange
相关产品推荐
相关产品推荐

