MST

星途 面试题库

面试题:C++ 中 memcpy() 与自定义内存复制函数的性能对比及优化

假设在 C++ 项目中,需要频繁进行大块内存的复制操作,现有一个自定义的内存复制函数 my_memcpy,其实现思路是逐字节复制。请分析 memcpy() 与 my_memcpy 在性能上的差异,并且说明如何对 my_memcpy 进行优化以接近 memcpy() 的性能。如果可以,给出优化后的代码示例。
24.4万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

性能差异分析

  1. memcpy
    • memcpy 是标准库函数,通常由编译器厂商高度优化,针对不同的硬件平台和 CPU 指令集进行了定制。例如,对于支持 SIMD(单指令多数据)指令集(如 SSE、AVX 等)的 CPU,memcpy 可能会利用这些指令一次性复制多个字节,大大提高复制效率。
    • 它在处理大块内存复制时,能够充分利用 CPU 的缓存机制,通过合理的预取和缓存命中策略,减少内存访问的延迟。
  2. my_memcpy
    • 逐字节复制的方式每次只复制一个字节,在 CPU 执行指令方面,会产生较多的指令开销,因为每次复制都需要一条指令。
    • 这种方式不能充分利用 CPU 的缓存优势,由于每次只访问一个字节,缓存命中率较低,特别是在大块内存复制时,频繁的内存访问会导致较大的延迟。

优化思路及代码示例

  1. 优化思路

    • 按字(word)复制:现代 CPU 通常以字(如 4 字节或 8 字节,取决于 CPU 架构)为单位进行内存访问效率更高。可以先按字复制,最后处理剩余不足一个字的字节。
    • 利用缓存预取:在复制之前,可以使用编译器特定的预取指令(如 _mm_prefetch 用于 x86 架构)来提前将数据预取到缓存中,提高缓存命中率。
    • SIMD 优化:如果目标平台支持 SIMD 指令集,可以利用这些指令一次性复制多个字节。不过,这需要更复杂的代码编写,并且要考虑不同 SIMD 指令集的兼容性。
  2. 按字复制优化后的代码示例

#include <cstddef>
#include <immintrin.h> // 用于缓存预取,需要支持 SSE 指令集的编译器

void* my_memcpy(void* dest, const void* src, size_t count) {
    char* d = static_cast<char*>(dest);
    const char* s = static_cast<const char*>(src);

    // 按 8 字节(64 位平台常见字大小)对齐复制
    size_t alignedCount = count & ~size_t(7);
    for (size_t i = 0; i < alignedCount; i += 8) {
        // 缓存预取
        _mm_prefetch(s + i + 64, _MM_HINT_T0);
        *(reinterpret_cast<unsigned long long*>(d + i)) = *(reinterpret_cast<const unsigned long long*>(s + i));
    }

    // 处理剩余字节
    for (size_t i = alignedCount; i < count; ++i) {
        d[i] = s[i];
    }

    return dest;
}

在上述代码中,首先按 8 字节对齐进行复制,同时利用 _mm_prefetch 指令进行缓存预取(假设编译器支持 SSE 指令集),最后处理剩余不足 8 字节的部分。这样优化后,my_memcpy 的性能可以更接近标准库的 memcpy

需要注意的是,不同编译器对特定指令的支持可能不同,实际应用中需要根据目标编译器和平台进行调整。同时,在进行 SIMD 优化时,代码会变得更加复杂,并且要充分考虑兼容性问题。