性能杀手:”潜伏”的memset

转载 2015年07月06日 20:18:24

性能杀手:”潜伏”的memset

memset性能陷进】

memset是大家常用的函数,而且一般的编程书籍都会谆谆告诫大家:申请内存后要初始化,防止使用未经初始化的内存导致不可预知的结果,所以我们一般都会按照如下方式编写代码:

char* buffer = (char*)malloc(1024);

memset(buffer, 0x00, 1024);

 

代码看起来很标准,也很美观,但这里却隐藏着一个陷进:memset的性能并不高!如果你的内存在1K左右,可能还察觉不到,但如果是1M,那性能影响就很明显了,我们来看实测数据(Redhat EL5 Intel(R) Xeon(R) CPU E5620 2.40GHz, gcc 4.1.2):

内存大小

重复次数

时间

1K

100000

14ms

1M

1000

60ms

10M

1000

725ms

 

所以如果你在代码里循环调用memset,或者每次流程处理都要调用memset,而且每次都要memset 1M以上,那么你就要小心了,1000次的memset 1M就能够消耗你100多ms的时间,对于高并发高性能的系统来说,这个时间是非常可观的,这就意味着即使不考虑业务处理,1秒钟你最多也不可能处理超过10000次请求。

 

【“潜伏”的memset

但这只是memset的第一个陷阱,还有更加隐蔽的第二个陷阱,而且这个陷阱你从代码上根本看不出和memset有什么关系!下面我们就来看这个“潜伏”的memset究竟是如何潜伏的

代码很简单,就是在栈内存中申请缓冲区,然后再赋值:

char buffer[1024] = {0};

这一行代码很简单,和上面代码不同的地方就在于一个在堆内存中申请,一个在栈内存中申请。但就是这么一行简单的代码,却隐藏了一个陷阱:初始化的时候调用了memset。我们用ltrace工具来看看:

===============================代码====================================

int main(){

   char buffer[1024 * 1024] = {0};

}

==============================ltrace 输出================================

__libc_start_main(0x8048444, 1, 0xbff9b0a4, 0x80484b0, 0x80484a0 <unfinished ...>

memset(0xbfe9afec, '/000', 1048576)                      = 0xbfe9afec

+++ exited (status 236) +++

 

可能会有同学会说,你全部都初始化为0了,当然慢了,如果我只初始化第一个元素,是不是会没有这个问题呢?

多说无益,代码验证:

===============================代码====================================

int main(){

   char buffer[1024 * 1024] = {0,};

}

==============================ltrace 输出================================

__libc_start_main(0x8048444, 1, 0xbfb54f84, 0x80484b0, 0x80484a0 <unfinished ...>

memset(0xbfa54ecc, '/000', 1048576)                      = 0xbfa54ecc

+++ exited (status 204) +++

 

从上面的ltrace输出可以看出,不管你是全部设置为0,还是只设置第一个,都会调用memset,而且memset的长度都是1024*1024,难道这行代码和括号里的值没有关系,无论怎样都会调用memset?我们再来验证一下:

===============================代码====================================

int main(){

   char buffer[1024 * 1024] = {};

}

==============================ltrace 输出================================

__libc_start_main(0x8048444, 1, 0xbff0e844, 0x80484b0, 0x80484a0 <unfinished ...>

memset(0xbfe0e78c, '/000', 1048576)                      = 0xbfe0e78c

+++ exited (status 140) +++

 

再验证括号里设置其它值:

===============================代码====================================

int main(){

   char buffer[1024 * 1024] = {100};

}

==============================ltrace 输出================================

__libc_start_main(0x8048444, 1, 0xbfea51b4, 0x80484b0, 0x80484a0 <unfinished ...>

memset(0xbfda50fc, '/000', 1048576)                      = 0xbfda50fc

+++ exited (status 252) +++

经过4次代码验证,确实证实了不管{}里面是什么内容,都会调用memset,将所有内存设置为0

实际上上面的代码里面还有一个陷阱,下面这两行语句的效果是一样的,第一行并不是将所有的内存位都设置为0,而只是设置第0个内存位为0.

char buffer[1024 * 1024] = {0};

char buffer[1024 * 1024] = {0,};

因为隐藏调用了memset,所以设置为0的时候还看不出,我们换一个值就看的很清楚了:

============================代码=======================================

#include <stdio.h>

int main(){

    char buffer[1024 * 1024 ] = {100};

    printf("%d/n", *buffer);

    printf("%d/n", *(buffer+1));

}

=============================输出======================================

100

0

 

各种赋值的情况我们已经验证过了,当然也要验证一下不赋值的情况,下面是验证情况:

===============================代码====================================

int main(){

char buffer[1024 * 1024 ];

*buffer = 'A';

printf("%d/n", *buffer);

}

==============================ltrace 输出================================

__libc_start_main(0x8048444, 1, 0xbfbab8f4, 0x80484b0, 0x80484a0 <unfinished ...>

printf("%d/n", 6565

)                                                                = 3

+++ exited (status 3) +++

从ltrace结果可以清楚的看出:即使使用了内存,这次也并没有调用memset了。

 

除了char类型外,其它的类型也都是这样的,有兴趣的同学可以自己尝试一下,这里就不贴详细的代码了。

 

【总结汇总】

代码

作用

性能(ms/times)

是否隐含调用memset

char buffer[1024 * 1024] = {0};

申请栈空间,并将第一个元素设置为0

75/1000

char buffer[1024 * 1024] ={0,};

75/1000

char buffer[1024 * 1024] = {};

申请栈空间,不设置任何元素

75/1000

char buffer[1024 * 1024] = {100};

申请栈空间,并将第一个元素设置为100

75/1000

char buffer[1024 * 1024 ];

只申请栈空间

5/1000000

 

综合前面的代码分析和性能测试,建议大家在编写代码的时候不要用char buffer[1024 * 1024] = XXX;这种方式,而要使用char buffer[1024 * 1024];除非确定一定要memset。

 

有的同学就会问到:既然这样,难道我们就不用memset了?

我的理解是:memset 1k以下,次数也不多,则可以用;否则除非你确定一定要memset,否则就不要用,你可以用其它替代方法。

例如:

1)如果内存是用来存放字符串的,你完全可以使用sprintf等函数,让其自动添加结束符;或者自己记录字符串的长度,在最后手动设置字符串结束符。

2)对于C++的struct来说,可以定义默认构造函数来完成初始化。


附:

使用Visual studio 2008测试,发现有同样的问题,初步估计是不是哪个标准定义了,但找了几个C/C++/POSIX的标准初看了一下,内容太多,都是英文的,一时半会看不出来,有兴趣有时间的同学可以去研究一下。


===========================================================================================

2014.10.10补充: 经过CSDN网友  @土鸡笨蛋 的提醒,然后自己试了一下,在gcc编译时加上 O2或者O3 优化参数,就不会隐含调用memset


非常感谢@土鸡笨蛋 的测试和补充,让这个知识点更全面了,由此也引出两个值得注意的点:

1)Debug模式和Release模式处理不一样,因此可能出现Debug模式下没有问题,正式发布上线后就有问题

2)研发开发的时候为了方便可以用Debug模式,但给测试同学测试的时候一定要用Release模式,否则就可能出现上述问题


欢迎转载,转载请注明出处:http://blog.csdn.net/yunhua_lee/article/details/6381866

版权声明:本文为博主原创文章,未经博主允许不得转载。

性能杀手:”潜伏”的memset

【memset性能陷进】 memset是大家常用的函数,而且一般的编程书籍都会谆谆告诫大家:申请内存后要初始化,防止使用未经初始化的内存导致不可预知的结果,所以我们一般都会按照如下方式编写代码: ...
  • bingqingsuimeng
  • bingqingsuimeng
  • 2013年10月09日 13:34
  • 899

bzero & memset置零的性能比较

 关于字符数组的初始化,在项目的压力测试中,发现性能明显下降,变怀疑在程序中的若干临时字符数组的初始化(使用bzero)身上。于是修改为首个字符置零的方式而非全部置零的方式初始化,响应得到明显的提升。...
  • taolinke
  • taolinke
  • 2009年08月26日 17:13
  • 980

潜伏的杀手

忙碌的办公族大部分不太注重自已的饮食习惯,等到胃病、糖尿病、肥胖这些疾病找上门时,方知是平时饮食坏习惯导致... 1.不吃早餐 严重伤胃,使你无法精力充沛地工作,而且还容易“显老”。德国埃朗根大学...
  • wtwobd
  • wtwobd
  • 2011年11月23日 10:46
  • 151

memset会显著增加时间和空间的消耗吗

刷OJ时,发现差不多的代码,室友的内存大约是我的一半。 经过详细的比对,发现问题出现在memset函数上面。const int maxx=1010; int a[maxx][maxx]; memse...
  • windrises
  • windrises
  • 2015年06月19日 02:15
  • 1204

小心NLS_SORT和NLS_COMP的设置成为性能杀手

NLS_SORT和NLS_COMP是Oracle两个初始化参数。顾名思义,NLS_SORT和NLS_COMP是设置排序和比较的方式。通过设置这两个参数可以实现自定义的排序和比较的方式。设置这两个参数的...
  • wh62592855
  • wh62592855
  • 2009年11月28日 19:59
  • 3266

关于性能测试时的隐性杀手--日志级别

前端时间,测试人员再做性能测试时,发现TPS 非常地低,一直搞不明白怎么回事。 查看线程堆栈,发现很多线程在 争夺 日志文件。最后发现日志级别为DEBUG,那个汗。。。。 由此,想了一下,...
  • yysct2005
  • yysct2005
  • 2013年04月22日 16:21
  • 804

NOIP2009提高组《潜伏者》解题报告

《NOIP2009潜伏者》解题报告 ----------------------by d
  • u012746396
  • u012746396
  • 2014年08月29日 16:32
  • 888

JavaScript 性能优化杀手

原址:http://web.jobbole.com/83582/?utm_source=blog.jobbole.com&utm_medium=relatedPosts 引言 ...
  • wuyou1336
  • wuyou1336
  • 2017年01月12日 23:00
  • 94

bzero和memset的区别

1)void *memset(void *s,int c,size_t n)         总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。        2).memset...
  • hello_world6
  • hello_world6
  • 2016年12月06日 23:17
  • 2339

系统性能调优经验

在开发对外提供服务的模块的时候,系统的性能经常会是令我们头疼的问题,具体系统性能的定义与瓶颈的定位方法,可以参考陈皓的这篇文章: 性能调优攻略http://coolshell.cn/articles/...
  • daofengliu
  • daofengliu
  • 2013年08月22日 19:11
  • 2293
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:性能杀手:”潜伏”的memset
举报原因:
原因补充:

(最多只允许输入30个字)