初始化效率问题:高分求memset效率理论与实践证明

HelloCChao 2010-02-08 10:06:16
在定义数组时直接初始化和然后再使用memset初始化,哪个效率更高?
例:
#define ARRAYSIZE 2048
void main()
{
char arrayA[ARRAYSIZE] = {0};
char arrayB[ARRAYSIZE];
memset(array, 0, ARRAYSIZE);
}


对于 char arrayA[ARRAYSIZE] = {0}; 几乎所有编译器都是把后续全部初始化为0的。这种方式的初始化的原理是什么呢?

上例中,arrayA和arrayB都会被初始化成0.到底哪个效率更高点呢?最好直接给出证据证明。
...全文
1549 35 打赏 收藏 转发到动态 举报
写回复
用AI写文章
35 条回复
切换为时间正序
请发表友善的回复…
发表回复
bladesoft 2010-02-08
  • 打赏
  • 举报
回复
建议还是用memset,有利于提高你的coding style。
forster 2010-02-08
  • 打赏
  • 举报
回复
char arrayA[ARRAYSIZE] = {0};
直接就变成memset吧
traceless 2010-02-08
  • 打赏
  • 举报
回复
一者闻道有先后;
traceless 2010-02-08
  • 打赏
  • 举报
回复
一者闻到有先后;

二者有些人总免不了要钻牛角尖,不会联想。

推荐读物:一、深入理解计算机;二、运用底层语言思想编写高级语言代码
yutaooo 2010-02-08
  • 打赏
  • 举报
回复

#18L 循环展开是个惯用技巧,是个好话题。

LZ 的,是个无聊的话题。

深入理解计算机系统号称“与等重的黄金等价”,深入探讨了这一系列问题。远比“循环展开”要深入。推荐读一读。
luvi88 2010-02-08
  • 打赏
  • 举报
回复
我认为这点效率就不用太放心上了,一次初始化过程,这点损失微乎其微,如果是要多次初始化,那你赋初值的方式根本也解决不了这种问题,而必须用memset()之类的了。不要把精力纠结与此了,放在功能、算法的优化上,优化占大量时间的功能(复杂度高的部分)块才是真道理
  • 打赏
  • 举报
回复
再贴一次16元素的时候的版本
 4    : 	char a[16]={0};

mov BYTE PTR _a$[ebp], 0
xor eax, eax
mov DWORD PTR _a$[ebp+1], eax
mov DWORD PTR _a$[ebp+5], eax
mov DWORD PTR _a$[ebp+9], eax
mov WORD PTR _a$[ebp+13], ax
mov BYTE PTR _a$[ebp+15], al

; 5 :
; 6 : char b[16];
; 7 : memset(b,0,sizeof(b));

push 16 ; 00000010H
push 0
lea ecx, DWORD PTR _b$[ebp]
push ecx
call _memset
add esp, 12 ; 0000000cH

; 8 :
; 9 : char c[16]={};

xor edx, edx
mov DWORD PTR _c$[ebp], edx
mov DWORD PTR _c$[ebp+4], edx
mov DWORD PTR _c$[ebp+8], edx
mov DWORD PTR _c$[ebp+12], edx
traceless 2010-02-08
  • 打赏
  • 举报
回复
[Quote=引用 21 楼 akirya 的回复:]
贴错格式了 。。。。
Assembly code; 4 : char a[100]={0};mov BYTE PTR _a$[ebp],0push99; 00000063Hpush0lea eax, DWORD PTR _a$[ebp+1]push eaxcall _memsetadd esp,12; 0000000cH; 5 :
; 6 : char b[100];
; 7 : memset(b,0,sizeof(b));push100; 00000064Hpush0lea ecx, DWORD PTR _b$[ebp]push ecxcall _memsetadd esp,12; 0000000cH; 8 :
; 9 : char c[100]={};push100; 00000064Hpush0lea edx, DWORD PTR _c$[ebp]push edxcall _memsetadd esp,12; 0000000cH
[/Quote]

就这样了,很明显了

traceless 2010-02-08
  • 打赏
  • 举报
回复
是的,如18L描叙的那样。同等条件下,循环次数越少,效率越高。

效率高不高终归要看指令个数以及指令格式
  • 打赏
  • 举报
回复
贴错格式了 。。。。

; 4 : char a[100]={0};

mov BYTE PTR _a$[ebp], 0
push 99 ; 00000063H
push 0
lea eax, DWORD PTR _a$[ebp+1]
push eax
call _memset
add esp, 12 ; 0000000cH

; 5 :
; 6 : char b[100];
; 7 : memset(b,0,sizeof(b));

push 100 ; 00000064H
push 0
lea ecx, DWORD PTR _b$[ebp]
push ecx
call _memset
add esp, 12 ; 0000000cH

; 8 :
; 9 : char c[100]={};

push 100 ; 00000064H
push 0
lea edx, DWORD PTR _c$[ebp]
push edx
call _memset
add esp, 12 ; 0000000cH
  • 打赏
  • 举报
回复
VC9编译出来的代码,自己比较下那个快。

; 4    : 	char a[100]={0};

mov BYTE PTR _a$[ebp], 0
push 99 ; 00000063H
push 0
lea eax, DWORD PTR _a$[ebp+1]
push eax
call _memset
add esp, 12 ; 0000000cH

; 5 :
; 6 : char b[100];
; 7 : memset(b,0,sizeof(b));

push 100 ; 00000064H
push 0
lea ecx, DWORD PTR _b$[ebp]
push ecx
call _memset
add esp, 12 ; 0000000cH

; 8 :
; 9 : char c[100]={};

push 100 ; 00000064H
push 0
lea edx, DWORD PTR _c$[ebp]
push edx
call _memset
add esp, 12 ; 0000000cH
DontKissBossAss 2010-02-08
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 dontkissbossass 的回复:]
引用 9 楼 akirya 的回复:
codechar arrayB[ARRAYSIZE]= {};


坏 老师, 动态的也能这么着初始化?我去试试去
[/Quote]

运来还是初始化第一个元素。
phpjspasp 2010-02-08
  • 打赏
  • 举报
回复
第一部分是glibc中的DIR/string/memset.c的内容
第二部分是glibc中的DIR/string/test-memset.c的内容
先看第二部分,函数的名字叫simple_memset,很简单,一个字节一个字节地去赋值。
再看第一部分代码,
其中 :
#define op_t unsigned long int
#define OPSIZ sizeof(op_t)
先假设OPSIZ==4,当然,等于8的时候同样适用。


#include <string.h>
#include <memcopy.h>

#undef memset

void *
memset (dstpp, c, len)
void *dstpp;
int c;
size_t len;
{
long int dstp = (long int) dstpp;

if (len >= 8)
{
size_t xlen;
op_t cccc;

cccc = (unsigned char) c;
cccc |= cccc << 8;
cccc |= cccc << 16;
if (OPSIZ > 4)
/* Do the shift in two steps to avoid warning if long has 32 bits. */
cccc |= (cccc << 16) << 16;

/* There are at least some bytes to set.
No need to test for LEN == 0 in this alignment loop. */
while (dstp % OPSIZ != 0) // 使dstp对齐为OPSIZ的整数倍。
{
((byte *) dstp)[0] = c;
dstp += 1;
len -= 1;
}

/* Write 8 `op_t' per iteration until less than 8 `op_t' remain. */
xlen = len / (OPSIZ * 8); //一次写8个op_t,如果op_t是4,一次写32字节,
while (xlen > 0) //如果op_t是8,一次写64字节
{
((op_t *) dstp)[0] = cccc;
((op_t *) dstp)[1] = cccc;
((op_t *) dstp)[2] = cccc;
((op_t *) dstp)[3] = cccc;
((op_t *) dstp)[4] = cccc;
((op_t *) dstp)[5] = cccc;
((op_t *) dstp)[6] = cccc;
((op_t *) dstp)[7] = cccc;
dstp += 8 * OPSIZ;
xlen -= 1;
}
len %= OPSIZ * 8;

/* Write 1 `op_t' per iteration until less than OPSIZ bytes remain. */
xlen = len / OPSIZ; //可能还有剩下的几个op_t没有初始化的,肯定小于8个
while (xlen > 0)
{
((op_t *) dstp)[0] = cccc;
dstp += OPSIZ;
xlen -= 1;
}
len %= OPSIZ;
}

/* Write the last few bytes. */
while (len > 0)//可能还有几个字节没有初始化
{
((byte *) dstp)[0] = c;
dstp += 1;
len -= 1;
}

return dstpp;
}





char *
simple_memset (char *s, int c, size_t n)
{
char *r = s, *end = s + n;
while (r < end)
*r++ = c;
return s;
}


从glibc的memset.c可以看出来,memset分以下几步:
1:先将地址对齐
2:一次循环写8个op_t。(我们看到,一次循环写8个op_t,肯定比8次循环写8个op_t效率高,因为一次循环只跳转一次,8次循环要跳转8次)
3:剩下的不足8个op_t,一次写一个op_t。
4:还有剩下的不足一个op_t,一次写一个字节。

和simple_memset相比,大多数赋值一次写一个op_t,总线效率应该比一次写一个字节要高。memset.c尽力减少循环次数,因为一次循环就意味着要判断要不要跳转,所以,循环次数越少,效率应该越高。

char arrayA[ARRAYSIZE] = {0};
效率很难比用memset高,因为综上分析,我觉得给一块内存赋值的效率很难超越memset。
也就是说,如果编译器给一块内存赋值的效率比memset的效率还高,memset完全可以再优化成编译用的方法。


如果上面的说法有误,请指正。
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 traceless 的回复:]
哦,这样呀,我估计是不是也和数据对齐有关
[/Quote]
就是对齐的关系。

不过一般写代码没必要纠结这种级别的代码优化。特殊情况例外
mLee79 2010-02-08
  • 打赏
  • 举报
回复
memcpy 啥的函数一般都是 builtin 的, 能把这搞出差别的编译器实在是太有才了....
traceless 2010-02-08
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 akirya 的回复:]
引用 13 楼 akirya 的回复:
引用 12 楼 traceless 的回复:
同意9L

  如果要精细到单个指令的话,char arrayA[ARRAYSIZE]= {0}这个比9L提到到的两种说法还多个指令

  晚上回去翻翻书,书里也差不多是这样的解释吧,我想的。。。

还有一点
通常memset的实现是   对sizeof(void*)倍数的地址效率高。

少了两个字
通常memset的实现通常是  对sizeof(void*)倍数的地址效率高。
[/Quote]

哦,这样呀,我估计是不是也和数据对齐有关
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 akirya 的回复:]
引用 12 楼 traceless 的回复:
同意9L

如果要精细到单个指令的话,char arrayA[ARRAYSIZE]= {0}这个比9L提到到的两种说法还多个指令

晚上回去翻翻书,书里也差不多是这样的解释吧,我想的。。。

还有一点
通常memset的实现是  对sizeof(void*)倍数的地址效率高。
[/Quote]
少了两个字
通常memset的实现通常是 对sizeof(void*)倍数的地址效率高。
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 traceless 的回复:]
同意9L

如果要精细到单个指令的话,char arrayA[ARRAYSIZE]= {0}这个比9L提到到的两种说法还多个指令

晚上回去翻翻书,书里也差不多是这样的解释吧,我想的。。。
[/Quote]
还有一点
通常memset的实现是 对sizeof(void*)倍数的地址效率高。
traceless 2010-02-08
  • 打赏
  • 举报
回复
同意9L

如果要精细到单个指令的话,char arrayA[ARRAYSIZE]= {0}这个比9L提到到的两种说法还多个指令

晚上回去翻翻书,书里也差不多是这样的解释吧,我想的。。。
traceless 2010-02-08
  • 打赏
  • 举报
回复
不同意说因为使用memset(array,0, ARRAYSIZE)这种方法会把程序体积搞大的说法。
程序的体积从根本来说也是程序指令的体积。
二者的指令几乎一样,除了刚开始的寻址方式外。

char arrayB[ARRAYSIZE];
memset(array,0, ARRAYSIZE);这种方法还是很常见常用的。

再者论效率,只能说第一种写代码的效率高点点而已。就好像用在C语言里用正则表达式,能说正则表达式
的执行效率高吗,只能说代码简洁,写的速度高而已。
加载更多回复(15)

69,371

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧