QNX 平台下 mmap 缓存与非缓存模式的 memcpy 性能分析

1. 引言

在嵌入式系统中,数据处理效率对系统性能至关重要。在 QNX 系统中,通过 mmap 函数将文件映射到内存后,可以选择启用或禁用缓存(Cache)。本文将对比在缓存(Cache)和非缓存(NoCache)情况下,使用 memcpy 和汇编版本 asm_memcpy 的数据传输速度,帮助开发者优化系统性能。

2. 为什么要做 Cache 与 NoCache 对比?

缓存(Cache)可以显著提升系统性能,因为它允许 CPU 以更快的速度访问频繁使用的数据。然而,在某些场景下,如实时系统或对数据一致性要求较高的应用,使用缓存可能引发数据同步问题或增加不必要的延迟。因此,在特定情况下,禁用缓存(NoCache)可能更为合适。

3. mmap nocache 简介

在 QNX 系统中,mmap 函数可以将文件或设备内存映射到进程的地址空间。通过使用 MAP_NOCACHE 标志,映射的内存页不会被缓存到文件系统中,而是直接关联到文件。这种配置适用于频繁读写大量数据的场景,因为它减少了缓存管理的开销,提高了数据传输的实时性。

名词解释

  • mmap cached:指通过 PROT_READ | PROT_WRITE 标志进行内存映射,内存页会被缓存到文件系统缓存中。
  • mmap nocached:指通过 PROT_READ | PROT_WRITE | MAP_NOCACHE 标志进行内存映射,内存页不会被缓存到文件系统缓存中。

示例代码

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd = open("file.txt", O_RDWR);
    char *ptr = mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NOCACHE, fd, 0);
    // 使用 ptr 直接从文件中读取和写入数据
    // ...
    munmap(ptr, 100);
    close(fd);
    return 0;
}

4. 实验数据对比

4.1 测试环境

  • 系统:QNX Neutrino RTOS 7.1
  • 硬件:ARM Cortex-A53 系列处理器

4.2 测试设置

本文的测试代码分为以下几部分:

  1. mmap cached:使用缓存的 mmap 映射。
  2. mmap nocached:禁用缓存的 mmap 映射。
  3. memcpy:标准的 C 语言 memcpy 函数。
  4. invalidate memcpy:在 memcpy 前使用 msync 进行缓存同步。
  5. asm memcpy:基于 ARM 汇编语言实现的快速 memcpy

4.3 测试数据

以下表格展示了不同情况下的内存拷贝速度(单位:MB/s):

测试场景memcpy (cached)memcpy (nocached)invalidate memcpy (cached)invalidate memcpy (nocached)asm memcpy (cached)asm memcpy (nocached)
速度 (MB/s)2133.333333116.3636361333.333333112.2807022133.333333225.352113

4.4 代码示例

memcpy

标准的内存拷贝操作,适用于大多数场景。

for (size_t i = 0; i < count; i++) {
    memcpy(dst, src[i], bytes);
}
invalidate memcpy

在调用 memcpy 前,通过 msync 函数将共享内存区的内容无效化,以确保数据的一致性。

for (size_t i = 0; i < count; i++) {
    msync(src[i], bytes, MS_INVALIDATE);
    memcpy(dst, src[i], bytes);
}
asm memcpy

使用汇编指令优化的内存拷贝函数,以最大化性能,适用于对性能要求极高的场景。

inline void aarch64_fast_memcpy(void *dst, const void *src, size_t size) {
#ifdef _QNX_
    void *ss = (void *)src, *dd = (void *)dst;
    size_t sz = size;

    asm volatile("loop_start: "
                 "ldp q3, q4,[%0,#0x0]\n"
                 "ldp q5, q6,  [%0,#0x20]\n"
                 "ldp q7, q8,  [%0,#0x40]\n"
                 "ldp q9, q10, [%0,#0x60]\n"
                 "stp q3, q4,  [%1,#0x0]\n"
                 "stp q5, q6,  [%1,#0x20]\n"
                 "stp q7, q8, [%1,#0x40]\n"
                 "stp q9, q10, [%1,#0x60]\n"
                 "add %0, %0, #0x80\n"
                 "add %1, %1, #0x80\n"
                 "subs %2, %2, #0x80\n"
                 "b.ne loop_start\n"
                 "dsb sy\n"
                 : /* no output */
                 : "r"(ss), "r"(dd), "r"(sz));
#endif
}
// ......   
for (size_t i = 0; i < count; i++) {
    aarch64_fast_memcpy(dst, src[i], bytes);
}

aarch64_fast_memcpy 直接使用了 SIMD(Single Instruction Multiple Data)指令,如 ldp 和 stp,这些指令可以一次加载和存储128位(两个64位的寄存器),在处理大块数据时能极大地提高吞吐量。aarch64_fast_memcpy 的实现通过批量加载和存储数据,可能更好地利用了 CPU 的流水线和内存带宽。

5. 分析与结论

5.1 Cache 模式下的测试结果

  • memcpy cached: 2133.33 MB/s
  • invalidate memcpy cached: 1333.33 MB/s
  • asm memcpy cached: 2133.33 MB/s

在启用缓存的情况下,标准的 memcpy 和汇编实现的 asm memcpy 表现出了相同的高效速度。这表明,在大多数情况下,标准库的 memcpy 已经进行了充分的优化。invalidate memcpy 因为多了一步缓存同步操作,所以速度相对较慢。

5.2 NoCache 模式下的测试结果

  • memcpy nocached: 116.36 MB/s
  • invalidate memcpy nocached: 112.28 MB/s
  • asm memcpy nocached: 225.35 MB/s

在禁用缓存的情况下,汇编实现的 asm memcpy 明显比标准的 memcpy 更快。这是因为在没有缓存的情况下,直接访问内存的速度会受到更大的影响,而汇编实现的优化使得它能够在更低的开销下处理数据。

5.3 结论

通过实验数据,可以得出以下结论:

  1. 缓存情况下memcpyasm memcpy 的速度几乎相同,均表现出极高的效率,而 invalidate memcpy 的速度明显较慢。这表明在缓存开启的情况下,标准 memcpy 已经非常高效,使用汇编优化的内存拷贝不会显著提升性能。

  2. 非缓存情况下asm memcpy 的速度远超其他方法,尤其是标准 memcpyinvalidate memcpy。这说明在非缓存场景下,使用汇编优化的内存拷贝可以显著提升数据传输效率,是性能敏感应用的优选。

6. 结论

在 QNX 系统中,启用缓存可以显著提高数据传输的效率;而禁用缓存时,使用汇编优化的 asm memcpy 能够提供更好的性能。实际应用中,选择是否启用缓存应根据具体的应用场景和性能需求来决定。

7. 附完整测试C++源码

// mmap_memcpy.c
#include <errno.h>
#include <fcntl.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

#ifndef _QNX_
#define PROT_NOCACHE 0
#endif

inline void aarch64_fast_memcpy(void *dst, const void *src, size_t size) {
#ifdef _QNX_
    void *ss = (void *)src, *dd = (void *)dst;
    size_t sz = size;

    asm volatile("loop_start: "
                 "ldp q3, q4,[%0,#0x0]\n"
                 "ldp q5, q6,  [%0,#0x20]\n"
                 "ldp q7, q8,  [%0,#0x40]\n"
                 "ldp q9, q10, [%0,#0x60]\n"
                 "stp q3, q4,  [%1,#0x0]\n"
                 "stp q5, q6,  [%1,#0x20]\n"
                 "stp q7, q8, [%1,#0x40]\n"
                 "stp q9, q10, [%1,#0x60]\n"
                 "add %0, %0, #0x80\n"
                 "add %1, %1, #0x80\n"
                 "subs %2, %2, #0x80\n"
                 "b.ne loop_start\n"
                 "dsb sy\n"
                 : /* no output */
                 : "r"(ss), "r"(dd), "r"(sz));
#endif
}

off_t offset(unsigned int bytes) {
    static off_t base_offset = 0x1E000000;
    off_t return_base_offset = base_offset;
    base_offset += bytes;
    return return_base_offset;
}

void *mmap_memory(unsigned int bytes, int flag) {
    int fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd < 0) {
        printf("open /dev/mem failed: %s\n", strerror(errno));
    }

    void *ptr = mmap(NULL, bytes, flag, MAP_SHARED, fd, offset(bytes));
    close(fd);

    if (MAP_FAILED == ptr) {
        printf("mmap failed: %s\n", strerror(errno));
    }

    return ptr;
}

void *mmap_memory_cached(unsigned int bytes) {
    return mmap_memory(bytes, PROT_READ | PROT_WRITE);
}

void *mmap_memory_nocached(unsigned int bytes) {
    return mmap_memory(bytes, PROT_READ | PROT_WRITE | PROT_NOCACHE);
}

// if C++
#ifdef __cplusplus
#include <chrono>
using namespace std::chrono;
#define start() auto start_ = high_resolution_clock::now();
#define end()                                                                  \
    auto end_ = high_resolution_clock::now();                                  \
    double bytes_mb = bytes * count / 1024.0 / 1024.0;                         \
    double cost_ns = duration_cast<nanoseconds>(end_ - start_).count();        \
    double mps = bytes_mb / cost_ns * 1e9;

#else
#include <time.h>
#include <unistd.h>
double time_diff_ns(struct timespec start, struct timespec end) {
    return (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec);
}
#define start()                                                                \
    struct timespec start_, end_;                                              \
    clock_gettime(CLOCK_REALTIME, &start_);

#define end()                                                                  \
    clock_gettime(CLOCK_REALTIME, &end_);                                      \
    double bytes_mb = bytes * count / 1024.0 / 1024.0;                         \
    double cost_ns = time_diff_ns(start_, end_);                               \
    double mps = bytes_mb / cost_ns * 1e9;

#endif

double memcpy_speed(void *dst, void **src, unsigned int bytes,
                    unsigned int count) {
    start();
    for (size_t i = 0; i < count; i++) {
        memcpy(dst, src[i], bytes);
    }
    end();
    return mps;
}

double invalidate_memcpy_speed(void *dst, void **src, unsigned int bytes,
                               unsigned int count) {
    start();
    for (size_t i = 0; i < count; i++) {
        msync(src[i], bytes, MS_INVALIDATE);
        memcpy(dst, src[i], bytes);
    }
    end();
    return mps;
}

double asm_memcpy_speed(void *dst, void **src, unsigned int bytes,
                        unsigned int count) {
    start();
    for (size_t i = 0; i < count; i++) {
        aarch64_fast_memcpy(dst, src[i], bytes);
    }
    end();
    return mps;
}

int main(int argc, char *argv[]) {
    const unsigned int count = 2;
    unsigned int bytes = 

128 * 1024; // 128 KB
    if (argc > 1) {
        bytes = atoi(argv[1]) * 1024;
    }
    printf("bytes: %d\n", bytes);
    printf("count: %d\n", count);

    void *mmap_cached_src[count];
    void *mmap_nocached_src[count];
    for (size_t i = 0; i < count; i++) {
        mmap_cached_src[i] = mmap_memory_cached(bytes);
        mmap_nocached_src[i] = mmap_memory_nocached(bytes);
    }

    void *dst = malloc(bytes);
    printf("memcpy cached speed: %f MB/s\n",
           memcpy_speed(dst, mmap_cached_src, bytes, count));
    printf("memcpy nocached speed: %f MB/s\n",
           memcpy_speed(dst, mmap_nocached_src, bytes, count));
    printf("invalidate memcpy cached speed: %f MB/s\n",
           invalidate_memcpy_speed(dst, mmap_cached_src, bytes, count));
    printf("invalidate memcpy nocached speed: %f MB/s\n",
           invalidate_memcpy_speed(dst, mmap_nocached_src, bytes, count));
    printf("asm memcpy cached speed: %f MB/s dst[0]=%d\n",
           asm_memcpy_speed(dst, mmap_cached_src, bytes, count), ((char *)dst)[0]);
    printf("asm memcpy nocached speed: %f MB/s\n",
           asm_memcpy_speed(dst, mmap_nocached_src, bytes, count));
    free(dst);
    for (size_t i = 0; i < count; i++) {
        munmap(mmap_cached_src[i], bytes);
        munmap(mmap_nocached_src[i], bytes);
    }

    return 0;
}

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值