使用GCC编译,mem_demo_1编译成60行汇编代码,而mem_demo_2只编译成20行。性能差异也非常大。我已决定在Linux 2.6.32上使用gcc 4.4.6验证这个说法。首先,mem_demo_1编译成60行汇编代码,而mem_demo_2只编译成20行。
.
这是测试(在文件main.c中):
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char* mem_demo_1(char *j)
{
memset(j, 0, 64);
return j;
}
char* mem_demo_2(void)
{
char * j = malloc(64);
memset(j, 0, 64);
return j;
}
int main()
{
char *p;
p = malloc(64);
p = mem_demo_1(p);
printf ("%p\n",p);
free (p);
p = mem_demo_2();
printf ("%p\n",p);
free (p);
return 0;
}
当我编译时:
gcc -fno-inline -fno-builtin -m64 -g -O2 main.c -o main.no_inline_no_builtin
我看到 mem_demo_1 中只有 8 行:
(gdb) disassemble mem_demo_1
Dump of assembler code for function mem_demo_1:
0x00000000004005d0 <+0>: push %rbx
0x00000000004005d1 <+1>: mov $0x40,%edx
0x00000000004005d6 <+6>: mov %rdi,%rbx
0x00000000004005d9 <+9>: xor %esi,%esi
0x00000000004005db <+11>: callq 0x400470 <memset@plt>
0x00000000004005e0 <+16>: mov %rbx,%rax
0x00000000004005e3 <+19>: pop %rbx
0x00000000004005e4 <+20>: retq
End of assembler dump.
我看到 mem_demo_2 中只有 11 行:
(gdb) disassemble mem_demo_2
Dump of assembler code for function mem_demo_2:
0x00000000004005a0 <+0>: push %rbx
0x00000000004005a1 <+1>: mov $0x40,%edi
0x00000000004005a6 <+6>: callq 0x400480 <malloc@plt>
0x00000000004005ab <+11>: mov $0x40,%edx
0x00000000004005b0 <+16>: mov %rax,%rbx
0x00000000004005b3 <+19>: xor %esi,%esi
0x00000000004005b5 <+21>: mov %rax,%rdi
0x00000000004005b8 <+24>: callq 0x400470 <memset@plt>
0x00000000004005bd <+29>: mov %rbx,%rax
0x00000000004005c0 <+32>: pop %rbx
0x00000000004005c1 <+33>: retq
End of assembler dump.
所以,“mem_demo_1编译成60行汇编,而mem_demo_2编译成20行”无法确认。
当我编译时:
gcc -m64 -g -O2 main.c -o main.default
gcc使用自己的memset实现,而mem_demo_1和mem_demo_2两个函数都更大:
mem_demo_1: 43 instructions
mem_demo_2: 48 instructions
然而,“mem_demo_1编译成60行汇编,而mem_demo_2编译成20行”也无法得到证实。
第二个问题
“性能差异也很大”
我扩展了main.c以便使用memset进行大量循环。我也没有看到mem_demo_1中的memset比mem_demo_2中的更慢。
这是来自Linux perf报告的:
mem_demo_2在memset中花费了8.37%:
8.37% main.perf.no_bu libc-2.12.so [.] __memset_sse2
而mem_demo_1在memset中花费了7.61%:
7.61% main.perf.no_bu libc-2.12.so [.] __memset_sse2
这些是测量结果本身:
# time ./main.perf.no_builtin_no_inline 100000000 1 0
number loops 100000000
mem_demo_1
real 0m3.483s
user 0m3.481s
sys 0m0.002s
# time ./main.perf.no_builtin_no_inline 100000000 2 0
number loops 100000000
mem_demo_2
real 0m3.503s
user 0m3.501s
sys 0m0.001s
顺便说一下,这是 gcc -fverbose-asm -c -S -O3
显示 mem_demo_2 汇编代码的方法:
char* mem_demo_2(void)
{
char * j = malloc(64);
memset(j, 0, 64);
return j;
}
.file "main.mem_demo_2.c"
mem_demo_2:
.LFB30:
.file 1 "main.mem_demo_2.c"
.loc 1 6 0
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
.loc 1 7 0
movl $64, %edi
call malloc
.loc 1 8 0
testb $1, %al
.loc 1 7 0
movq %rax, %rsi
.LVL0:
.loc 1 8 0
movq %rax, %rdi
movl $64, %edx
jne .L10
testb $2, %dil
jne .L11
.L3:
testb $4, %dil
jne .L12
.L4:
movl %edx, %ecx
xorl %eax, %eax
.LVL1:
shrl $3, %ecx
testb $4, %dl
mov %ecx, %ecx
rep stosq
je .L5
movl $0, (%rdi)
addq $4, %rdi
.L5:
testb $2, %dl
je .L6
movw $0, (%rdi)
addq $2, %rdi
.L6:
andl $1, %edx
je .L7
movb $0, (%rdi)
.L7:
.loc 1 10 0
movq %rsi, %rax
addq $8, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 8
ret
.p2align 4,,10
.p2align 3
.L10:
.cfi_restore_state
.loc 1 8 0
leaq 1(%rax), %rdi
movb $0, (%rax)
movb $63, %dl
testb $2, %dil
je .L3
.p2align 4,,10
.p2align 3
.L11:
movw $0, (%rdi)
addq $2, %rdi
subl $2, %edx
testb $4, %dil
je .L4
.p2align 4,,10
.p2align 3
.L12:
movl $0, (%rdi)
subl $4, %edx
addq $4, %rdi
jmp .L4
.cfi_endproc
char *
传递给memset
函数,编译器无法对其进行任何对齐假设。如果你将一个long *
传递给memset
函数,编译器可以假定内存块按照long
字节对齐,并且这使得memset
函数更加高效。 - David Schwartzmemset
,而该函数不对内存对齐作出任何假设。因此,你不仅得到了最坏情况下的memset
,还多了一个跳转和返回操作! - David Schwartz