免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 5522 | 回复: 14
打印 上一主题 下一主题

[C] 关于memcpy 效率问题[致敬帖] [复制链接]

论坛徽章:
1
程序设计版块每日发帖之星
日期:2016-06-04 06:20:00
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2016-06-01 10:21 |只看该作者 |倒序浏览
本帖最后由 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[3*j], &src[j], 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

哇靠,才发现真的是大牛而不是在调侃的。
敬礼!
-----------------

回到上面关于memcpy()的问题,
各位有什么建议吗?
谢谢。

论坛徽章:
3
2015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:032015年亚洲杯之中国
日期:2015-04-22 15:52:45
2 [报告]
发表于 2016-06-01 11:51 |只看该作者
必然性+反复性的做内存拷贝

我觉得没办法, 有好办法记得在这里写一记.
-----------------------------------------------------------------

PS, 努力自写一份, 绝大多数也达不到标准库的水平. 考虑上业务的特殊性做特别的优化.
性能提升也不会超过20%.


不过提问题, 直接限定"必然+反复"的条件, 似乎限定了讨论的范畴, 如果讲清楚使用场景
可能大家会想到更好的办法.

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
3 [报告]
发表于 2016-06-01 12:32 |只看该作者
本帖最后由 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的时候, 检测一次, 设置一个标志就可以了.
否则, 一堆的检测代码和一堆的判断, 乱不说, 效率也低.

论坛徽章:
1
程序设计版块每日发帖之星
日期:2016-06-04 06:20:00
4 [报告]
发表于 2016-06-01 12:40 |只看该作者
hanxin83 发表于 2016-06-01 11:51
必然性+反复性的做内存拷贝

rb>>准确来说,每一秒都需要有120次左右的,把一部分数据移动到另外一个地方,或者说,把A,B,C 3个地方的数据,参差 的组合在一起。

我觉得没办法, 有好办法记得在这里写一记.


rb>>的确,一般人写的,不太可能到达/超越libc,glibc里面的API,
除非,有某些 “其他” 方式,或者做这件事情,有其他更好的API,
比如说memmove()--这里只是举一个例子说明对内存、数据操作,除了memcpy()还有memmove()之类的,并非表达memmove()比memcpy()高效。
或者说,能直接调用类似于 内核态的API  或者 比较通用的DMA什么的。。。

论坛徽章:
1
程序设计版块每日发帖之星
日期:2016-06-04 06:20:00
5 [报告]
发表于 2016-06-01 12:42 |只看该作者
本帖最后由 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[m*j], &src[j], 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左右。

论坛徽章:
3
2015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:032015年亚洲杯之中国
日期:2015-04-22 15:52:45
6 [报告]
发表于 2016-06-01 13:05 |只看该作者
robin10 发表于 2016-06-01 12:40
rb>>的确,一般人写的,不太可能到达/超越libc,glibc里面的API,
除非,有某些 “其他” 方式,或者做 ...


既然不方便说架构和场景的话, 那我就只能提供一个关键字你看有用没有用了.
remap_file_pages

论坛徽章:
324
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52双子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午马
日期:2013-10-18 21:43:38
7 [报告]
发表于 2016-06-01 13:29 |只看该作者
修改设计避免拷贝是最好的办法,如果没有办法,那就memcpy

论坛徽章:
1
程序设计版块每日发帖之星
日期:2016-06-04 06:20:00
8 [报告]
发表于 2016-06-01 13:54 |只看该作者
兄弟误会了,倒不是不方便,只是为了避免无用信息干扰,
尽可能的把无关信息过滤掉了而已,
并且这只是其中的非常小的一部分。。。却是必须用到的部分(也就是为什么说 每秒都要执行100次左右的原因)。

回复 6# hanxin83


   

论坛徽章:
1
程序设计版块每日发帖之星
日期:2016-06-04 06:20:00
9 [报告]
发表于 2016-06-01 13:56 |只看该作者
这种比较 深入 的内容,没怎么了解。。。
回复 3# wlmqgzm


   

论坛徽章:
1
程序设计版块每日发帖之星
日期:2016-06-04 06:20:00
10 [报告]
发表于 2016-06-01 14:03 |只看该作者
坦白说,其实对这个的优化,并没有抱着太大的希望,
因为相信能经得起99%的人使用、检验的API,当然不是吹的(起码里面99%左右应该是比较 极限 的了吧)。
反而比较好奇,
为什么连续做1000次,10000次循环memcpy(),和我做一次,消耗的时间完全不是线性的。

为了避免“每次都是拷贝一样的数据到一样的地址段,(拷贝之前对比了)内容一样,所以直接跳过” 这这可能性,
我也尽可能的每次都往src中放入不同的数据,并且拷贝到dst的不同位置(k%3),
但是效果几乎是一样的。

5222.png (2.59 KB, 下载次数: 74)

5222.png
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP