文章摘要
作者在优化Shadesmar项目时发现,对于大于512KB的未序列化消息,大部分执行时间都花在memcpy上,用于在进程内存和共享内存之间复制数据。通过分析perf工具的输出,发现__memmove_avx_unaligned_erms是memcpy的实现,使用AVX指令集每次复制32字节。作者尝试实现一种更快的内存复制方法,以提升性能。
文章总结
标题:超越memcpy的速度
主要内容:
在分析Shadesmar时,作者发现对于大于512kB的未序列化二进制消息,大部分执行时间都花在了使用memcpy在进程内存和共享内存之间复制消息上。为了优化这一过程,作者尝试了几种更快的内存复制方法。
memcpy的剖析
通过perf工具分析,作者发现__memmove_avx_unaligned_erms是memcpy的一个实现,它使用AVX指令集一次复制32字节。glibc中的memcpy实际上是memmove的实现,允许源和目标内存区域重叠。erms(增强重复移动)是一种硬件优化,用于简单的复制循环。vec表示使用向量化指令一次复制多个字节,而unaligned则表示该实现可以处理未对齐的内存指针。
优化方法
基本REP MOVSB:作者首先实现了一个简单的ERSB(增强重复移动字节)版本,使用内联汇编编写循环,避免编译器优化,仅依赖硬件优化。
对齐的AVX复制:由于作者可以控制内存分配,因此实现了一个仅处理对齐指针和内存大小的AVX复制版本,使用AVX指令集一次复制32字节。
流式对齐AVX复制:使用AVX的非临时(NT)加载和存储指令,跳过缓存,减少缓存开销。这种方法在数据量较大时表现更好。
带预取的流式对齐AVX复制:在复制过程中预取下一迭代的数据,进一步提升性能。每个循环迭代复制64字节,并预取下一64字节的数据。
其他优化途径
循环展开:通过展开循环减少分支语句的数量,提升性能。作者实现了4倍展开的AVX复制版本。
多线程:将数据分段,使用多线程并行复制,尤其适用于多核CPU。
Shadesmar API
为了便于集成自定义内存复制逻辑,作者引入了Copier概念,允许用户实现自定义的复制算法。Copier类提供了内存分配、释放以及内存复制的接口。作者还实现了MTCopier,为其他复制器添加多线程支持。
基准测试
作者使用Google的Benchmark工具测试了不同复制方法在32kB到64MB数据范围内的性能。测试平台为AMD Ryzen 7 3700X,16GB DDR4内存。
结论
对于大多数情况,std::memcpy已经提供了很好的性能,并且适应不同的硬件架构,无需假设内存对齐。如果性能至关重要,可以考虑使用更具体的非通用实现,如带预取的流式复制在大数据量时表现最佳,但在小数据量时性能较差。循环展开在大多数情况下能提升5-10%的性能,但在某些情况下可能不如未展开的版本。
代码
所有方法的代码都包含在Shadesmar库中,作者将其命名为dragons.h,并附上警告:“此处有龙”,提醒用户使用这些自定义复制器时的潜在风险。
作者:Dheeraj R Reddy,2020年5月24日
评论总结
评论主要围绕内存拷贝优化、性能测试和硬件缓存等问题展开,观点多样且各有侧重。
内存拷贝优化:
- waschl 提出零拷贝IPC的设想,建议发送方直接在共享内存中分配负载,以避免内存拷贝的开销。
引用:
"I guess it would be best if the sender allocates its payload directly on the shared memory when it’s created."
“我认为发送方在创建时直接在共享内存中分配负载是最好的。” - brucehoult 认为
std::memcpy在大多数情况下表现优异,尤其适应硬件架构且不假设内存对齐,但在RISC-V机器上,自定义的向量化拷贝可能更快。
引用:
"Stick tostd::memcpy. It delivers great performance while also adapting to the hardware architecture."
“坚持使用std::memcpy,它在适应硬件架构的同时提供了出色的性能。”
- waschl 提出零拷贝IPC的设想,建议发送方直接在共享内存中分配负载,以避免内存拷贝的开销。
性能测试与硬件缓存:
- userbinator 指出内存拷贝基准测试中常见的问题是未确保数据在目标位置完全序列化和访问,导致结果不准确。
引用:
"A common problem I’ve seen in the past with memory copying benchmarks is to not serialise and access the copied data in its destination."
“过去在内存拷贝基准测试中常见的问题是未在目标位置序列化和访问拷贝的数据。” - Arech 质疑作者是否控制了硬件缓存,认为缺乏这一控制的结果毫无意义。
引用:
"It’s not clear how the author controlled for HW caching. Without this, the results are, unfortunately, meaningless."
“不清楚作者如何控制硬件缓存,缺乏这一控制的结果毫无意义。”
- userbinator 指出内存拷贝基准测试中常见的问题是未确保数据在目标位置完全序列化和访问,导致结果不准确。
多线程与DMA:
- adwn 对多线程加速内存拷贝表示怀疑,认为单核已能饱和内存控制器的带宽,除非在NUMA系统或L2缓存间拷贝。
引用:
"A single core in a desktop CPU can easily saturate the bandwidth of the system RAM controller."
“桌面CPU的单核很容易饱和系统内存控制器的带宽。” - kvemkon 提出疑问,既然DMA可以在设备和RAM之间高效拷贝数据,为何不能用于RAM到RAM的拷贝。
引用:
"Why we can’t use DMA to copy RAM-to-RAM?"
“为什么我们不能用DMA来拷贝RAM到RAM?”
- adwn 对多线程加速内存拷贝表示怀疑,认为单核已能饱和内存控制器的带宽,除非在NUMA系统或L2缓存间拷贝。
图表与结果质疑:
- PaulHoule 指出优化拷贝仅在缓存内小缓冲区(如4k)表现更好,大缓冲区下与标准库拷贝性能相近。
引用:
"The better performance is only for small buffer sizes which fit in the cache."
“优化拷贝仅在适合缓存的小缓冲区中表现更好。” - mojo-ponderer 对图表中的性能跳跃表示怀疑,认为数据传输速度的变化不合理。
引用:
"It just doesn’t make sense."
“这完全不合理。”
- PaulHoule 指出优化拷贝仅在缓存内小缓冲区(如4k)表现更好,大缓冲区下与标准库拷贝性能相近。
总结:评论者对内存拷贝优化、性能测试方法和硬件缓存控制提出了多种观点,部分人支持标准库的 memcpy,也有人质疑测试结果的准确性和图表的合理性。多线程加速和DMA的应用也引发了讨论。