堆到共享内存的memcpy性能为何极差?
堆到共享内存与堆到堆的memcpy性能差异分析
测试内容
我测试了堆到堆、堆到共享内存(通过shm_open)的memcpy性能,测试代码如下:
头文件 shm_msg.hpp
// shm_msg.hpp #ifndef _SHM_MSG_HPP_ #define _SHM_MSG_HPP_ #include <fcntl.h> #include <semaphore.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <array> #include <stdint.h> #define BUF_SIZE 100 * 1024 * 1024 /* Maximum size */ typedef struct shmbuf { uint32_t a; double b; std::array<uint8_t, BUF_SIZE> data; uint64_t c; } shmbuf; #endif // !_SHM_MSG_HPP_
测试代码 shm_openWriter.cpp
// shm_openWriter.cpp #include "shm_msg.hpp" #include <iostream> #include <ctype.h> #include <vector> #include <cstring> #include <chrono> int main(int argc, char *argv[]) { /* Create shared memory object and set its size to the size of our structure. */ int fd = shm_open("SHM_1", O_CREAT | O_EXCL | O_RDWR, 0600); if (fd == -1) { perror("shm_open"); } size_t size = sizeof(shmbuf); if (ftruncate(fd, size) == -1) { perror("ftruncate"); } /* Map the object into the caller's address space. */ shmbuf *shmp = (shmbuf *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (shmp == MAP_FAILED) { perror("mmap"); } /* Copy data into the shared memory object. */ std::vector<uint8_t> vec(BUF_SIZE, 0x56); vec[100] = 0x89; shmp->a = 5; shmp->b = 2.5; shmp->c = 100L; std::chrono::high_resolution_clock::time_point tp1 = std::chrono::high_resolution_clock::now(); std::memcpy(shmp->data.data(), vec.data(), BUF_SIZE); std::chrono::high_resolution_clock::time_point tp2 = std::chrono::high_resolution_clock::now(); printf("data[100] = 0x%X\n", shmp->data[100]); std::cout << "a = " << shmp->a << std::endl; std::cout << "b = " << shmp->b << std::endl; std::cout << "c = " << shmp->c << std::endl; std::cout << "memcpy time from heap to SHM = " << std::chrono::duration_cast<std::chrono::nanoseconds>(tp2 - tp1).count() / 1e6 << " ms." << std::endl; std::vector<uint8_t> vec2(BUF_SIZE); std::chrono::high_resolution_clock::time_point tp3 = std::chrono::high_resolution_clock::now(); // std::memcpy(shmp->data.data(), vec.data(), BUF_SIZE); // wrong line std::memcpy(vec2.data(), vec.data(), BUF_SIZE); // new line based on @RaymondChen's answer. std::chrono::high_resolution_clock::time_point tp4 = std::chrono::high_resolution_clock::now(); std::cout << "memcpy time from heap to heap = " << std::chrono::duration_cast<std::chrono::nanoseconds>(tp4 - tp3).count() / 1e6 << " ms." << std::endl; shm_unlink("SHM_1"); exit(EXIT_SUCCESS); }
编译与运行
g++ shm_openWriter.cpp -o shm_openWriter ./shm_openWriter
测试输出
data[100] = 0x89 a = 5 b = 2.5 c = 100 memcpy time from heap to SHM = 239.019 ms. memcpy time from heap to heap = 14.3007 ms.
初步结论与补充测试
堆到共享内存的memcpy速度比堆到堆慢约17倍。后续补充测试结果:
- SHM到SHM的
memcpy首次拷贝较慢,后续速度有所提升,但仍不及堆到堆的拷贝性能; - 在
mmap时添加MAP_POPULATE标志后,堆到SHM的memcpy性能大幅提升,耗时接近堆到堆的拷贝。
问题解答
性能差异的原因
- 按需页分配与缺页中断:共享内存通过
mmap映射后,内核仅建立虚拟地址到共享对象的映射关系,并未立即分配物理内存页。首次向共享内存写入数据时,会触发大量缺页中断,内核需要为每个页分配物理内存、更新页表,这个过程会带来显著的额外开销。而堆内存属于匿名映射,物理页在内存分配时已提前完成,写入时无此开销。 - 内存缓存行为差异:堆内存的数据在创建后已加载到CPU缓存中,拷贝时能直接利用缓存加速;而共享内存的物理页首次分配后,数据尚未进入缓存,需要从内存(或tmpfs的后端存储)加载,缓存命中率低导致拷贝速度慢。
- 共享内存的额外内核操作:
shm_open创建的共享内存基于tmpfs文件系统,写入时内核需要维护文件系统的元数据一致性,相比堆内存的直接物理页操作,多了一层文件系统的处理开销。
优化方法
- 使用
MAP_POPULATE预分配页:在mmap时添加该标志,内核会提前为共享内存分配物理页并完成映射,避免首次写入时的缺页中断,这是最直接有效的优化方式,已在测试中验证有效。 - 预加载共享内存:在正式拷贝前,对共享内存区域进行一次遍历或写入操作(比如写入一个字节到每个页),触发页分配和缓存加载,后续拷贝即可达到正常速度。
- 使用大页(HugeTLB):如果系统支持,在
mmap时添加MAP_HUGETLB标志,使用大页减少页表项数量和缺页次数,大幅提升大内存块的拷贝效率。 - 避免不必要的拷贝:直接在共享内存区域内构造和操作数据,而非先在堆上创建数据再拷贝到共享内存,从根源上消除拷贝开销。
- 确保内存对齐:保证拷贝的源地址和目标地址按缓存行(64字节)或页(4KB/2MB)对齐,
memcpy本身会做优化,但对齐的地址能让CPU的内存访问更高效,减少不必要的内存拆分访问。
内容的提问来源于stack exchange,提问作者allblue lai
相关产品推荐
相关产品推荐

