关于memcpy 效率问题[致敬帖]
本帖最后由 robin10 于 2016-06-01 10:58 编辑先交代一下背景:
由于最近代码里面经常性。。或者说是必然性+反复性的做内存拷贝,
使用的是memcpy(dst, src, size),
大概情况如下:
1, i5 core, 64bit, 4GB mem, laptop;
2,每秒钟大概都需要做100次数据拷贝;
3,一次拷贝的时候,需要拷贝一共约为 1024Byte*循环1000次;
4,src的内存空间是连续的,即src的数据全部在1M的空间里面(假设总数据量是3所述的1024Byte*1000);
5,但是,存放到dst的空间,却不是连续的;
4,5两点的意思大概是:
然后,src以一行(假设以1024Byte)为单位,
src的0,1,2,.....,n....行,
会存放到dst的 0,3,6,....,3n,....行里面;
这里应该意味着,不可以直接“块拷贝”之类的(?)。
伪代码如下:
char* src = (char*) malloc(sizeof(char) * 1024 * 1024);
char* dst = (char*) malloc(sizeof(char) * 1024 * 1024 * 3);
int j;
for(int i = 0; i < 1000; i ++) {
j = 1024*i;
memcpy(&dst, &src, 1024);
}
根据1描述的环境,测试的时候大致留意了一下,每次拷贝数据,大约需要2ms(目前还不是release版本),
今天突然想起:
有没有比memcpy() 更高效的做法呢?最好当然是有API了。。。
(针对某一些硬件平台做的优化不在讨论之列)
------------------------
搜索了一下,发现有一个帖子:
http://bbs.chinaunix.net/thread-2320072-1-1.html
7L:
我是memcpy的作者,如果你发现效率很低麻烦您告诉我,谢谢。
---------
哈哈,好吧,第一反应我以为是 @linguranus 在调侃...
然后在知乎再次看到这位大牛的回帖:
https://www.zhihu.com/question/35172305/answer/61584927
哇靠,才发现真的是大牛而不是在调侃的。:mrgreen:
敬礼!
-----------------
回到上面关于memcpy()的问题,
各位有什么建议吗?
谢谢。
必然性+反复性的做内存拷贝
我觉得没办法, 有好办法记得在这里写一记.
-----------------------------------------------------------------
PS, 努力自写一份, 绝大多数也达不到标准库的水平. 考虑上业务的特殊性做特别的优化.
性能提升也不会超过20%.
不过提问题, 直接限定"必然+反复"的条件, 似乎限定了讨论的范畴, 如果讲清楚使用场景
可能大家会想到更好的办法. 本帖最后由 wlmqgzm 于 2016-06-01 12:53 编辑
1) 多数人写的未必比标准库更优, 因此, 多数人优化的思路应该是: 减少大数据块的Copy, 实现Zero Copy, 看能否用类似shared_ptr的指针技术来代替, 这样, 可能改进的余地更大.
2) 即使是标准库, 随着CPU指令集的添加, 一些新指令集会大幅度改善性能, 因此, 使用新指令集的库性能更好.估计今后, 经过 SSE4.2, AVX这些指令集改进过的代码, 慢慢也会陆续推广使用.毕竟加个链接库, 就可以提升性能.
看知乎的帖子中, 已经分别有SSE4.2和AVX两种指令集memcpy了, 性能都比原生的速度快很多, 但是只限于特定型号CPU, 这样的东西是无法进入到标准库的.
标准库最大的优点是: 兼容性最好, 适合任何CPU.
SSE4.2的性能确实很夸张, 测试过SSE的64bit crc32并发, 就是一条指令读入8字节执行crc32校验, 基本瓶颈都在内存上, 校验速度跟读内存的速度是一样的, 并在此基础上封装了一个高性能crc64/crc32的库自用.
为了兼容, 要附加很多代码, 包括检测CPU的型号和指令集, 对于支持Intel SSE4.2的做一个bool标记, 对于不支持的, 要会调用传统CPU指令的计算模块,
好在是做了C++ singleton class,这样只在程序启动后第一次construct的时候, 检测一次, 设置一个标志就可以了.
否则, 一堆的检测代码和一堆的判断, 乱不说, 效率也低. hanxin83 发表于 2016-06-01 11:51 static/image/common/back.gif
必然性+反复性的做内存拷贝
rb>>准确来说,每一秒都需要有120次左右的,把一部分数据移动到另外一个地方,或者说,把A,B,C 3个地方的数据,参差 的组合在一起。
我觉得没办法, 有好办法记得在这里写一记.
rb>>的确,一般人写的,不太可能到达/超越libc,glibc里面的API,
除非,有某些 “其他” 方式,或者做这件事情,有其他更好的API,
比如说memmove()--这里只是举一个例子说明对内存、数据操作,除了memcpy()还有memmove()之类的,并非表达memmove()比memcpy()高效。
或者说,能直接调用类似于 内核态的API或者 比较通用的DMA什么的。。。 本帖最后由 robin10 于 2016-06-01 12:53 编辑
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
int main(void)
{
int i;
int j;
int k;
int m;
struct timeval start_t;
struct timeval end_t;
char* src = (char*) malloc(sizeof(char) * 1024 * 1024);
char* dst = (char*) malloc(sizeof(char) * 1024 * 1024 * 3);
memset(src, 0x56, 1024*1024);
memset(src, 0x00, 1024*1024);
gettimeofday(&start_t, NULL);
printf("#start#%ld.%ld#\n", start_t.tv_sec, start_t.tv_usec);
for(k = 0; k < 1000; k ++) {
// memset(src, k, 1024*1024);
m =k%3;
for(i = 0; i < 1000; i ++) {
j = 1024*i;
memcpy(&dst, &src, 1024);
}
}
gettimeofday(&end_t, NULL);
printf("#end#%ld.%ld#\n\n", end_t.tv_sec, end_t.tv_usec);
printf("#dlt=%02ld Sec %06ld uSec#\n", end_t.tv_sec - start_t.tv_sec, end_t.tv_usec - start_t.tv_usec );
return 0;
}
测试了一下,遇到一个有意思的现象:
i5, 4G memory, ubuntu12.04,64bit.
如果只是拷贝 一次 ,即没有for(k = 0; k < 1000; k ++) {...} 循环,
需要大概3.3ms
但是,
如果循环1000次,却只需要70ms左右,
如果每次使用 memset(src, k, 1024*1024);
大概90ms左右。
robin10 发表于 2016-06-01 12:40 static/image/common/back.gif
rb>>的确,一般人写的,不太可能到达/超越libc,glibc里面的API,
除非,有某些 “其他” 方式,或者做 ...
既然不方便说架构和场景的话, 那我就只能提供一个关键字你看有用没有用了.
remap_file_pages 修改设计避免拷贝是最好的办法,如果没有办法,那就memcpy 兄弟误会了,倒不是不方便,只是为了避免无用信息干扰,
尽可能的把无关信息过滤掉了而已,
并且这只是其中的非常小的一部分。。。却是必须用到的部分(也就是为什么说 每秒都要执行100次左右的原因)。
回复 6# hanxin83
:shock: 这种比较 深入 的内容,没怎么了解。。。
回复 3# wlmqgzm
坦白说,其实对这个的优化,并没有抱着太大的希望,
因为相信能经得起99%的人使用、检验的API,当然不是吹的(起码里面99%左右应该是比较 极限 的了吧)。
反而比较好奇,
为什么连续做1000次,10000次循环memcpy(),和我做一次,消耗的时间完全不是线性的。
为了避免“每次都是拷贝一样的数据到一样的地址段,(拷贝之前对比了)内容一样,所以直接跳过” 这这可能性,
我也尽可能的每次都往src中放入不同的数据,并且拷贝到dst的不同位置(k%3),
但是效果几乎是一样的。
页:
[1]
2