memcpy如何优化拷贝

本文深入解析了memcpy函数的工作原理及优化方法。介绍了glibc2.5中memcpy的源码实现,包括字节拷贝和字节块拷贝的具体过程。特别针对x86平台,详细解释了非Pentium平台与Pentium平台下的wordcopy实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

memcpy如何优化拷贝

本文主要介绍下memcpy()的工作原理,其中采用的优化方法以及思路值得学习。


以下为glibc2.5中的memcpy的源码:

void *
memcpy (void* dstpp, const void* srcpp, size_t len)
{
unsigned long int dstp = (long int) dstpp;
unsigned long int srcp = (long int) srcpp;
//如果需要拷贝的字节数大于临界值,则会使用优化方法进行拷贝
if (len >= OP_T_THRES)   //根据不同的情况,OP_T_THRES定义为16或8
    {
      len -= (-dstp) % OPSIZ; //小技巧,很值得学习
      BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ); //按照字节进行对齐
      PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len); //对于特殊平台可能使用虚拟页拷贝
      WORD_COPY_FWD (dstp, srcp, len, len); //大字节拷贝
    }
BYTE_COPY_FWD (dstp, srcp, len);
return dstpp;
}

整个memcpy的流程为:
1. 判断需要拷贝的字节数是否大于某一临界值。如果大于临界值,则可以使用更加强大的优化手段进行拷贝。否则,直接转6。
2. 假设要拷贝的目的地如下所示:

                           
其中start为拷贝目的地的起始地址,end为拷贝目的地的结束地址,align border为内存中的对齐边界。在大多数平台下,从内存对齐边界开始拷贝会有许多的优化方法可以使用,此处memcpy正是利用了这点。
3. 计算start到align border的距离,此处使用了一个非常聪明的小技巧。使用 (-dstp) % OPSIZ 来计算start到align border的距离,这样可以减少一次判断。然后使用字节拷贝的方法来拷贝start到align border之间的内存。
4. 对于特殊平台,可能使用page copy的方法。由于限制条件较多,一般x86平台下不会使用。
5. 使用word copy的方法进行字节块拷贝,此处是memcpy优化的关键,优化的条件是拷贝地址处于对齐边界。在pentium系列平台和非pentium系列平台下,word copy有两种实现方式。
6. 剩余的不能采用word copy的尾部使用字节拷贝。

以下为x86平台下字节拷贝和字节块拷贝的实现
字节拷贝的实现:
#define BYTE_COPY_FWD(dst_bp, src_bp, nbytes)                                      /
do {                                                                              /
    int __d0;                                                                      /
    asm volatile(/* Clear the direction flag, so copying goes forward. */    /
                 "cld/n"                                                      /
                 /* Copy bytes. */                                              /
                 "rep/n"                                                      /
                 "movsb" :                                                      /
                 "=D" (dst_bp), "=S" (src_bp), "=c" (__d0) :                      /
                 "0" (dst_bp), "1" (src_bp), "2" (nbytes) :                      /
                 "memory");                                                      /
} while (0)
没啥好说的,利用x86的movsb指令实现字节拷贝。使用movsb指令时,需设置EDI,ESI,ECX寄存器的值,EDI寄存器存放拷贝的目的地 址,ESI寄存器存放拷贝的源地址,ECX为需要拷贝的字节数。拷贝完成之后,EDI中的值会保存到dst_bp中,ESI中的值会保存到src_bp 中。这也是为什么memcpy中没有出现对dst_bp操作的原因。

非Pentium平台下的word copy的实现:
#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes)                      /
do                                                                              /
    {                                                                              /
      int __d0;                                                                      /
      asm volatile(/* Clear the direction flag, so copying goes forward. */ /
                   "cld/n"                                                      /
                   /* Copy longwords. */                                      /
                   "rep/n"                                                      /
                   "movsl" :                                                      /
                    "=D" (dst_bp), "=S" (src_bp), "=c" (__d0) :                      /
                   "0" (dst_bp), "1" (src_bp), "2" ((nbytes) / 4) :              /
                   "memory");                                                      /
      (nbytes_left) = (nbytes) % 4;                                              /
    } while (0)
利用x86的movsl指令实现四字节拷贝。如果movsl和movsb花费相同的cpu时钟周期,那优化后的拷贝时间将是原来的四分之一。恩,相当可观了。。。

Pentium平台下的word copy的实现:
#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes)                /
do                                                                        /
    {                                                                        /
      asm volatile ("subl        $32,%2/n"                                /
                    "js                2f/n"                                        /
                    "movl        0(%0),%%edx/n"        /* alloc dest line */        /
                    "1:/n"                                                /
                    "movl        28(%0),%%eax/n"        /* alloc dest line */        /
                    "subl        $32,%2/n"        /* decr loop count */        /
                    "movl        0(%1),%%eax/n"        /* U pipe */                /
                    "movl        4(%1),%%edx/n"        /* V pipe */                /
                    "movl        %%eax,0(%0)/n"        /* U pipe */                /
                    "movl        %%edx,4(%0)/n"        /* V pipe */                /
                    "movl        8(%1),%%eax/n"                                /
                    "movl        12(%1),%%edx/n"                                /
                    "movl        %%eax,8(%0)/n"                                /
                    "movl        %%edx,12(%0)/n"                                /
                    "movl        16(%1),%%eax/n"                                /
                    "movl        20(%1),%%edx/n"                                /
                    "movl        %%eax,16(%0)/n"                                /
                    "movl        %%edx,20(%0)/n"                                /
                    "movl        24(%1),%%eax/n"                                /
                    "movl        28(%1),%%edx/n"                                /
                    "movl        %%eax,24(%0)/n"                                /
                    "movl        %%edx,28(%0)/n"                                /
                    "leal        32(%1),%1/n"        /* update src ptr */        /
                    "leal        32(%0),%0/n"        /* update dst ptr */        /
                    "jns        1b/n"                                        /
                    "2: addl        $32,%2" :                                /
                    "=r" (dst_bp), "=r" (src_bp), "=r" (nbytes_left) :        /
                    "0" (dst_bp), "1" (src_bp), "2" (nbytes) :                /
                    "ax", "dx");                                        /
    } while (0)
      字节块单元的大小变为了32。在执行过程中,利用Pentium平台下的pipeline技术。此处光看代码可能感觉不出来优化,但是联想一下Pentium平台下强大的流水线技术就会发现以上指令中的许多工作都可以并发执行,执行效率会大大提高。


参考来源:  http://blog.youkuaiyun.com/yalizhi123/article/details/5803503


在 C++ 中,`memcpy` 是一个非常高效的内存拷贝函数,但它本质上是一个低级别的操作,无法感知高级别的语义(如对象的构造、析构等)。如果需要优化 `memcpy` 的性能或扩展其功能,可以考虑以下几种方法: --- ### 回答问题 #### 如何优化 `memcpy` 拷贝? 1. **使用更高级别的拷贝机制**: - 对于复杂的数据结构(如类对象),直接使用 `memcpy` 可能会导致未定义行为。可以通过重载拷贝构造函数或赋值运算符来实现更安全的拷贝。 2. **避免不必要的拷贝**: - 使用移动语义(C++11 引入)可以避免深拷贝,从而提高性能。 - 示例:通过实现移动构造函数和移动赋值运算符,将资源的所有权从一个对象转移到另一个对象。 3. **使用 SIMD 指令**: - 如果目标平台支持 SIMD(Single Instruction Multiple Data),可以通过手动编写 SIMD 代码来加速内存拷贝。 - 示例:使用 `_mm_loadu_si128` 和 `_mm_storeu_si128` 等 intrinsic 函数。 4. **分块拷贝**: - 对于大块内存,可以将其分为多个小块进行拷贝,以减少缓存未命中带来的性能损失。 以下是优化 `memcpy` 的示例代码: ```cpp #include <iostream> #include <cstring> #include <immintrin.h> // SIMD 指令头文件 #include <vector> // 示例 1:使用移动语义优化拷贝 class MyClass { public: std::vector<int> data; // 拷贝构造函数 MyClass(const MyClass& other) : data(other.data) { std::cout << "Copy constructor called." << std::endl; } // 移动构造函数 MyClass(MyClass&& other) noexcept : data(std::move(other.data)) { std::cout << "Move constructor called." << std::endl; } // 拷贝赋值运算符 MyClass& operator=(const MyClass& other) { if (this != &other) { data = other.data; } std::cout << "Copy assignment called." << std::endl; return *this; } // 移动赋值运算符 MyClass& operator=(MyClass&& other) noexcept { if (this != &other) { data = std::move(other.data); } std::cout << "Move assignment called." << std::endl; return *this; } }; // 示例 2:使用 SIMD 指令优化 memcpy void simd_memcpy(void* dest, const void* src, size_t size) { const char* src_char = static_cast<const char*>(src); char* dest_char = static_cast<char*>(dest); // 使用 16 字节对齐的 SIMD 指令 for (size_t i = 0; i + 16 <= size; i += 16) { __m128i simd_data = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src_char + i)); _mm_storeu_si128(reinterpret_cast<__m128i*>(dest_char + i), simd_data); } // 处理剩余部分 for (size_t i = size / 16 * 16; i < size; ++i) { dest_char[i] = src_char[i]; } } int main() { // 示例 1:移动语义优化 MyClass obj1; obj1.data.resize(1000); MyClass obj2 = std::move(obj1); // 调用移动构造函数 // 示例 2:SIMD 优化 memcpy const size_t buffer_size = 1024; char src_buffer[buffer_size]; char dest_buffer[buffer_size]; std::memset(src_buffer, 0x5A, buffer_size); // 初始化源缓冲区 simd_memcpy(dest_buffer, src_buffer, buffer_size); // 使用 SIMD 优化memcpy return 0; } ``` --- ### 给出解释 #### 为什么需要优化 `memcpy`? 1. **安全性问题**: - `memcpy` 不会调用对象的拷贝构造函数或赋值运算符,因此对于包含动态内存分配或自定义逻辑的对象,直接使用 `memcpy` 可能会导致未定义行为。 2. **性能问题**: - 对于大块内存,`memcpy` 的性能可能受限于缓存未命中。通过分块拷贝或使用 SIMD 指令,可以显著提高性能。 3. **移动语义的优势**: - 在 C++11 中引入的移动语义允许将资源的所有权从一个对象转移到另一个对象,而无需深拷贝。这对于包含动态内存分配的对象尤为重要。 4. **SIMD 指令的作用**: - SIMD 指令可以在单个时钟周期内处理多个数据元素,从而显著提高内存拷贝的效率。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值