1.为什么会写memcpy
在之前的应聘笔试上遇到一道笔试题,题目要求实现一个my_memcpy函数。函数原型:void * my_memcpy(void *dst, const void *src, int n);
之前使用的内存拷贝函数是标准库memcpy函数,拿来就用,真没有对这个函数做过多了解。在网上查了一下,有好多关于memcpy函数优化的文章。
在实现过程中了解的越多,往往实现起来越麻烦。还是先实现简单的memcpy函数。
2.按字节(Byte)拷贝实现的memcpy
1 void *my_memcpy_byte(void *dst, const void *src, int n) 2 { 3 if (dst == NULL || src == NULL || n <= 0) 4 return NULL; 5 6 char * pdst = (char *)dst; 7 char * psrc = (char *)src; 8 9 if (pdst > psrc && pdst < psrc + n) 10 { 11 pdst = pdst + n - 1; 12 psrc = psrc + n - 1; 13 while (n--) 14 *pdst-- = *psrc--; 15 } 16 else 17 { 18 while (n--) 19 *pdst++ = *psrc++; 20 } 21 return dst; 22 }
在上面按字节拷贝中考虑了拷贝覆盖,连续的一段空间存放数据是从低地址到高地址进行存放。先从源地址读出数据,然后写入到目的地址空间中。目的空间的起始地址如果在源数据空间之内就会出现内存覆盖的情况。
这种情况先从尾部拷贝,避免覆盖数据,不过这种情况也会破坏src空间数据,在src前使用了const关键字,也就是空间只读,在函数内部不修改src空间数据。
而标准库的memcpy并没有将写覆盖认为是内存拷贝,而是内存移动。memcpy的src代表一块内存空间,并用const关键字修饰,并不希望内存块被破坏。
对于写覆盖(这里是内存块移动)标准库推荐使用memmove函数。
3.按4字节拷贝
1 void *my_memcpy(void *dst, const void *src, int n) 2 { 3 if (dst == NULL || src == NULL || n <= 0) 4 return NULL; 5 6 int * pdst = (int *)dst; 7 int * psrc = (int *)src; 8 char *tmp1 = NULL; 9 char *tmp2 = NULL; 10 int c1 = n / 4; 11 int c2 = n % 4; 12 13 if (pdst > psrc && pdst < psrc + n) 14 { 15 tmp1 = (char *)pdst + n - 1; 16 tmp2 = (char *)psrc + n - 1; 17 while(c2--) 18 *tmp1-- = *tmp2--; 19 pdst = (int *)tmp1; 20 psrc = (int *)tmp2; 21 while (c1--) 22 *pdst-- = *psrc--; 23 } 24 else 25 { 26 while (c1--) 27 *pdst++ = *psrc++; 28 tmp1 = (char *)pdst; 29 tmp2 = (char *)psrc; 30 while (c2--) 31 *tmp1++ = *tmp2++; 32 } 33 34 return dst; 35 }
这里还是考虑了写覆盖的代码。对比按字节拷贝,拷贝速度是提高不少。
以上是针对笔试过程中写memcpy。
4.如何优化memcpy
高性能的memcpy与很多因数相关,与平台,处理器,编译器,具体拷贝情形等相关。
VS2017中对C库的memcpy进行优化,glibc对memcpy也有优化
5.是否需要考虑内存对齐拷贝?
内存读写效率影响之一:内存对齐
参考:1.浅谈CPU内存访问要求对齐的原因 2.解析内存对齐
如果src,dst的地址是不对齐的,读写效率变低。
通过代码实现不对齐的拷贝,memcpy的实现会变得复杂,反而影响拷贝效率。
这种不对齐情况我们可以预先避免,因为编译器在给我们分配空间时是按照内存对齐进行分配的。
6.根据拷贝数据大小进行优化
1.多次调用memcpy,而每次拷贝数据大小Kb下的小拷贝
这种情况下尽量减少分支预测,代码精简。
2.拷贝Mb的memcpy实现
这种情况影响拷贝效率主要在寻址上。
7.总结
memcpy需要根据情况优化,如 平台,处理器,拷贝大小。