You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

堆到共享内存的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性能大幅提升,耗时接近堆到堆的拷贝。

问题解答

性能差异的原因

  1. 按需页分配与缺页中断:共享内存通过mmap映射后,内核仅建立虚拟地址到共享对象的映射关系,并未立即分配物理内存页。首次向共享内存写入数据时,会触发大量缺页中断,内核需要为每个页分配物理内存、更新页表,这个过程会带来显著的额外开销。而堆内存属于匿名映射,物理页在内存分配时已提前完成,写入时无此开销。
  2. 内存缓存行为差异:堆内存的数据在创建后已加载到CPU缓存中,拷贝时能直接利用缓存加速;而共享内存的物理页首次分配后,数据尚未进入缓存,需要从内存(或tmpfs的后端存储)加载,缓存命中率低导致拷贝速度慢。
  3. 共享内存的额外内核操作:shm_open创建的共享内存基于tmpfs文件系统,写入时内核需要维护文件系统的元数据一致性,相比堆内存的直接物理页操作,多了一层文件系统的处理开销。

优化方法

  1. 使用MAP_POPULATE预分配页:在mmap时添加该标志,内核会提前为共享内存分配物理页并完成映射,避免首次写入时的缺页中断,这是最直接有效的优化方式,已在测试中验证有效。
  2. 预加载共享内存:在正式拷贝前,对共享内存区域进行一次遍历或写入操作(比如写入一个字节到每个页),触发页分配和缓存加载,后续拷贝即可达到正常速度。
  3. 使用大页(HugeTLB):如果系统支持,在mmap时添加MAP_HUGETLB标志,使用大页减少页表项数量和缺页次数,大幅提升大内存块的拷贝效率。
  4. 避免不必要的拷贝:直接在共享内存区域内构造和操作数据,而非先在堆上创建数据再拷贝到共享内存,从根源上消除拷贝开销。
  5. 确保内存对齐:保证拷贝的源地址和目标地址按缓存行(64字节)或页(4KB/2MB)对齐,memcpy本身会做优化,但对齐的地址能让CPU的内存访问更高效,减少不必要的内存拆分访问。

内容的提问来源于stack exchange,提问作者allblue lai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 06:32:06