memcpyと構造体直接代入
直接代入が使えるのは知ってたけど、保証されている動作なのか判って
なかったので今まで使ってなかった。どうやらANSI CではOKと云うことらしい。
折角なので、どれだけ差が出るのか調べてみた。
サンプルコードは以下
struct addrinfo a1,a2; //memcpy経由 memcpy(&a1,&a2,sizeof(struct addrinfo)); //直接代入 a1 = a2;
- memcpy版のdisassemble出力 (gcc3.4.4 -O0)
0x080483c1 <main+33>: push $0x20 0x080483c3 <main+35>: lea 0xffffffb8(%ebp),%eax 0x080483c6 <main+38>: push %eax 0x080483c7 <main+39>: lea 0xffffffd8(%ebp),%eax 0x080483ca <main+42>: push %eax 0x080483cb <main+43>: call 0x80482e8 0x080483d0 <main+48>: add $0x10,%esp
- 構造体代入版のdisassemble出力 (gcc3.4.4 -O0)
0x080483da <main+58>: mov $0x8,%eax 0x080483df <main+63>: mov %eax,%ecx 0x080483e1 <main+65>: repz movsl %ds:(%esi),%es:(%edi)
と、構造体代入だと反復命令に展開される。確かに速いのも納得。
- memcpy版のdisassemble出力 (gcc3.4.4 -O2)
0x08048377 <main+15>: mov $0x8,%ecx 0x0804837c <main+20>: repz movsl %ds:(%esi),%es:(%edi)
- 構造体代入版のdisassemble出力 (gcc3.4.4 -O2)
0x08048380 <main+24>: mov $0x8,%ecx 0x08048385 <main+29>: repz movsl %ds:(%esi),%es:(%edi)
オプティマイズ指定すると両方とも同じコード出力となった。
ここで、addrinfoのサイズが0x20(32)byteでmovsl*8と丁度合って
たからなのかと思い、合ってないサイズで試してみた。
#以降は-O2において直接代入とmemcpyの結果は大体同じなので片方は省略。
- struct サイズ 33の場合 (gcc3.4.4 -O2)
0x08048388 <main+32>: mov $0x8,%cl 0x0804838a <main+34>: sub $0x18,%esp 0x0804838d <main+37>: repz movsl %ds:(%esi),%es:(%edi) 0x0804838f <main+39>: lea 0xffffffc8(%ebp),%eax 0x08048392 <main+42>: movsb %ds:(%esi),%es:(%edi)
repz movslとmovsbの組み合わせで表現されている。
更に大きいデータをコピーする場合はどうなるのかと思いこちらも試してみた。
- struct サイズ 1024の場合 (gcc3.4.4 -O2)
0x080483cc <main+44>: push $0x400 0x080483d1 <main+49>: push %ebx 0x080483d2 <main+50>: push %esi 0x080483d3 <main+51>: call 0x80482e8
memcpyを普通にcallするようになってしまった。
サイズを調整しながら何度か試したら64を越える場合はmemcpy処理になる模様。
で、問題のmemcpyの中は以下のような感じ。
0x0804dfa0 <memcpy+0>: push %ebp 0x0804dfa1 <memcpy+1>: mov %esp,%ebp 0x0804dfa3 <memcpy+3>: mov 0x8(%ebp),%edx 0x0804dfa6 <memcpy+6>: push %edi 0x0804dfa7 <memcpy+7>: cmpl $0x7,0x10(%ebp) 0x0804dfab <memcpy+11>: push %esi 0x0804dfac <memcpy+12>: mov %edx,%edi 0x0804dfae <memcpy+14>: mov 0xc(%ebp),%esi 0x0804dfb1 <memcpy+17>: jbe 0x804dfd0 <memcpy+48> 0x0804dfb3 <memcpy+19>: mov %edx,%ecx 0x0804dfb5 <memcpy+21>: neg %ecx 0x0804dfb7 <memcpy+23>: and $0x3,%ecx 0x0804dfba <memcpy+26>: mov 0x10(%ebp),%eax 0x0804dfbd <memcpy+29>: sub %ecx,%eax 0x0804dfbf <memcpy+31>: cld 0x0804dfc0 <memcpy+32>: repz movsb %ds:(%esi),%es:(%edi) 0x0804dfc2 <memcpy+34>: mov %eax,%ecx 0x0804dfc4 <memcpy+36>: shr $0x2,%ecx 0x0804dfc7 <memcpy+39>: cld 0x0804dfc8 <memcpy+40>: repz movsl %ds:(%esi),%es:(%edi) 0x0804dfca <memcpy+42>: and $0x3,%eax 0x0804dfcd <memcpy+45>: mov %eax,0x10(%ebp) 0x0804dfd0 <memcpy+48>: mov 0x10(%ebp),%ecx 0x0804dfd3 <memcpy+51>: cld 0x0804dfd4 <memcpy+52>: repz movsb %ds:(%esi),%es:(%edi) 0x0804dfd6 <memcpy+54>: pop %esi 0x0804dfd7 <memcpy+55>: mov %edx,%eax 0x0804dfd9 <memcpy+57>: pop %edi 0x0804dfda <memcpy+58>: leave 0x0804dfdb <memcpy+59>: ret 0x0804dfdc <memcpy+60>: nop 0x0804dfdd <memcpy+61>: nop 0x0804dfde <memcpy+62>: nop
まあ、やってることは同じでrepzプレフィックスによる反復実行でコピーしている。
小さいサイズの場合インライン化される様にgccが最適化してるんだろうなぁ。
結局の所どちらを使うか知り合いに聞いたらmemcpyの方が多かった。
そんな私もmemcpy派。