まずは普通に適当にベンチマークしてみる。
http://codepad.org/K1OkB8h1
Windows+MinGW(gcc4.5.0)の場合で、1000万個(SIZE)のデータに100回(LOOP)づつアクセス。
xorしているのは最適化によって使わない変数への処理を省いてしまうのを防止するため。
最適化なし(-O0)
array: 4187 clock
vector:5391 clock
最適化あり(-O2)
array: 921 clock
vector:1016 clock
ほら!最適化すればなかなか早いよ!!
バイナリも見る
OllyDbgが逆アセンブルした処理を眺める
最適化なし
配列
CPU Disasm Address Hex dump Command Comments 004013F4 |. E8 5F0B0000 CALL; Jump to msvcrt.clock 004013F9 |. 894424 24 MOV DWORD PTR SS:[ESP+24],EAX 004013FD |. C64424 3B 00 MOV BYTE PTR SS:[ESP+3B],0 00401402 |. EB 39 JMP SHORT 0040143D 00401404 |> C74424 3C 000 /MOV DWORD PTR SS:[ESP+3C],0 0040140C |. EB 1C |JMP SHORT 0040142A 0040140E |> 8B4424 3C |/MOV EAX,DWORD PTR SS:[ESP+3C] 00401412 |. 034424 28 ||ADD EAX,DWORD PTR SS:[ESP+28] 00401416 |. 8B5424 3C ||MOV EDX,DWORD PTR SS:[ESP+3C] 0040141A |. 035424 28 ||ADD EDX,DWORD PTR SS:[ESP+28] 0040141E |. 8A12 ||MOV DL,BYTE PTR DS:[EDX] 00401420 |. 325424 3B ||XOR DL,BYTE PTR SS:[ESP+3B] 00401424 |. 8810 ||MOV BYTE PTR DS:[EAX],DL 00401426 |. FF4424 3C ||INC DWORD PTR SS:[ESP+3C] 0040142A |> 817C24 3C 7F9 ||CMP DWORD PTR SS:[ESP+3C],98967F 00401432 |. 0F9EC0 ||SETLE AL 00401435 |. 84C0 ||TEST AL,AL 00401437 |.^ 75 D5 |\JNE SHORT 0040140E 00401439 |. FE4424 3B |INC BYTE PTR SS:[ESP+3B] 0040143D |> 807C24 3B 63 |CMP BYTE PTR SS:[ESP+3B],63 00401442 |. 0F9EC0 |SETLE AL 00401445 |. 84C0 |TEST AL,AL 00401447 |.^ 75 BB \JNE SHORT 00401404 00401449 |. E8 0A0B0000 CALL ; Jump to msvcrt.clock
vector
CPU Disasm Address Hex dump Command Comments 004014DF |. E8 740A0000 CALL; Jump to msvcrt.clock 004014E4 |. 894424 24 MOV DWORD PTR SS:[ESP+24],EAX 004014E8 |. C64424 3B 00 MOV BYTE PTR SS:[ESP+3B],0 004014ED |. EB 3D JMP SHORT 0040152C 004014EF |> C74424 3C 000 /MOV DWORD PTR SS:[ESP+3C],0 004014F7 |. EB 20 |JMP SHORT 00401519 004014F9 |> 8B4424 3C |/MOV EAX,DWORD PTR SS:[ESP+3C] 004014FD |. 894424 04 ||MOV DWORD PTR SS:[ESP+4],EAX 00401501 |. 8D4424 10 ||LEA EAX,[ESP+10] 00401505 |. 890424 ||MOV DWORD PTR SS:[ESP],EAX 00401508 |. E8 FF0D0000 ||CALL 0040230C 0040150D |. 8A10 ||MOV DL,BYTE PTR DS:[EAX] 0040150F |. 325424 3B ||XOR DL,BYTE PTR SS:[ESP+3B] 00401513 |. 8810 ||MOV BYTE PTR DS:[EAX],DL 00401515 |. FF4424 3C ||INC DWORD PTR SS:[ESP+3C] 00401519 |> 817C24 3C 7F9 ||CMP DWORD PTR SS:[ESP+3C],98967F 00401521 |. 0F9EC0 ||SETLE AL 00401524 |. 84C0 ||TEST AL,AL 00401526 |.^ 75 D1 |\JNE SHORT 004014F9 00401528 |. FE4424 3B |INC BYTE PTR SS:[ESP+3B] 0040152C |> 807C24 3B 63 |CMP BYTE PTR SS:[ESP+3B],63 00401531 |. 0F9EC0 |SETLE AL 00401534 |. 84C0 |TEST AL,AL 00401536 |.^ 75 B7 \JNE SHORT 004014EF 00401538 |. E8 1B0A0000 CALL ; Jump to msvcrt.clockmsvcrt.clock
vectorではループ中にCALLをしており、明らかに効率が悪そう。
1000clock以上の差が出るわけです。
最適化あり
配列
CPU Disasm Address Hex dump Command Comments 004013F3 |. E8 400B0000 CALL; Jump to msvcrt.clock 004013F8 |. 89C6 MOV ESI,EAX 004013FA |. 31D2 XOR EDX,EDX 004013FC |. 8D8B 80969800 LEA ECX,[EBX+989680] 00401402 |. 66:90 NOP ; Superfluous operand size prefix 00401404 |> 89D8 /MOV EAX,EBX 00401406 |. 66:90 |NOP ; Superfluous operand size prefix 00401408 |> 3010 |/XOR BYTE PTR DS:[EAX],DL 0040140A |. 40 ||INC EAX 0040140B |. 39C8 ||CMP EAX,ECX 0040140D |.^ 75 F9 |\JNE SHORT 00401408 0040140F |. 42 |INC EDX 00401410 |. 80FA 64 |CMP DL,64 00401413 |.^ 75 EF \JNE SHORT 00401404 00401415 |. E8 1E0B0000 CALL ; Jump to msvcrt.clock
ループに入る前(0x004013FC)にLEA命令で、アドレスの演算を行います。
EBXにはnewで確保したchar配列が1000万(=0x989680)個はいっているので、その終端をECXに代入。
その後ループに入り、EAXにEBXを代入し、EAXの先にある値をXORで計算し、EAXを1づつ増加させECXに到達したら、1000万回処理したことになり、ループを抜けます。
なんのこっちゃ、というと、添え字を使ったアクセスではないということです。
for(i=0; i!=n; ++i){ /* arr[i] を使った処理; */ }
というコードではなく、
char *earr = arr + n; for(char *parr=arr; arr!=earr; ++parr){ /* *parrを使った処理; */ }
という感じになっているわけです。C++のSTLのイテレータを使っているような感じです。
やってる事は同じですが、後者の方が早く動かせるのでしょう。
CPU Disasm Address Hex dump Command Comments 004014E0 |. E8 530A0000 CALL; Jump to msvcrt.clock 004014E5 |. 89C6 MOV ESI,EAX 004014E7 |. 31C9 XOR ECX,ECX 004014E9 |. 8D76 00 LEA ESI,[ESI] 004014EC |> 31C0 /XOR EAX,EAX 004014EE |. 66:90 |NOP ; Superfluous operand size prefix 004014F0 |> 8B5424 14 |/MOV EDX,DWORD PTR SS:[ESP+14] 004014F4 |. 01C2 ||ADD EDX,EAX 004014F6 |. 300A ||XOR BYTE PTR DS:[EDX],CL 004014F8 |. 40 ||INC EAX 004014F9 |. 3D 80969800 ||CMP EAX,989680 004014FE |.^ 75 F0 |\JNE SHORT 004014F0 00401500 |. 41 |INC ECX 00401501 |. 80F9 64 |CMP CL,64 00401504 |.^ 75 E6 \JNE SHORT 004014EC 00401506 |. E8 2D0A0000 CALL ; Jump to msvcrt.clock
XOR ECX,ECX でEAXを初期化(i=0に相当)
MOV EDX,DWORD PTR SS:[ESP+14] で、vectorが持つ配列へのアドレスをEDXに代入。
ADD EDX,EAX で、EDXのアドレスの位置を移動します。(EDX[i]に相当)
EDXに対する処理をして、
EAXを1加算し、1000万回になったらループを抜けます。
ということから、vectorでは添え字によるアクセスを行っていることがわかります。
配列はイテレータっぽくアクセスし、vectorでは添え字を使ったアクセスを行っている事になります。
その違いが、最適化の場合のベンチマーク結果の100clockの差でしょう。
ならなぜ、vectorの時も、配列で行われたような最適化をしないんだろうか?
vectorではsizeが記録されているので、イテレータのようにアクセスが出来るはずです。
(普通にiteratorを生成してたらそのオーバーヘッドを食ってしまう気もします)
配列の時と同じような最適化すれば、ほぼ同等な速度を出せるはずです。
アセンブリ言語と格闘してためしにやってみました。
CPU Disasm Address Hex dump Command Comments 004014F9 8D76 00 LEA ESI,[ESI] 004014FC 31C0 XOR EAX,EAX 004014FE 66:90 NOP ; Superfluous operand size prefix 00401500 8B5424 14 MOV EDX,DWORD PTR SS:[ESP+14] 00401504 01C2 ADD EDX,EAX 00401506 300A XOR BYTE PTR DS:[EDX],CL 00401508 |. 40 INC EAX 00401509 3D 80969800 CMP EAX,989680 0040150E ^ 75 F0 JNE SHORT 00401500 00401510 41 INC ECX 00401511 80F9 64 CMP CL,64 00401514 ^ 75 E6 JNE SHORT 004014FC
ここらへんを
CPU Disasm Address Hex dump Command Comments 004014F9 8B74E4 14 MOV ESI,DWORD PTR SS:[ESP+14] 004014FD 8D96 80969800 LEA EDX,[ESI+989680] 00401503 . 90 NOP 00401504 89F0 MOV EAX,ESI 00401506 > 3008 XOR BYTE PTR DS:[EAX],CL 00401508 . 40 INC EAX 00401509 39D0 CMP EAX,EDX 0040150B . 90 NOP 0040150C .^ 75 F8 JNE SHORT 00401506 0040150E . 41 INC ECX 0040150F . 80F9 64 CMP CL,64 00401512 .^ 75 F0 JNE SHORT 00401504 00401514 90 NOP 00401515 90 NOP
こんなふうにすれば
array: 906 clock
vector:891 clock
誤差程度になります:)
LEA EDX,[ESI+989680]の989680は、要素の数(==10,000,000)なんですが、
コレは最適化によって定数展開されたものです。
この値は実際はsize()なりで現在の要素数を得るべきなんですが、まぁこれは定数時間で終わる処理だし、そのコード埋め込むのも面倒だからいいよね。ってことで決めうちしてます。
使ったバイナリ晒し
http://rying.net/arc/vector_vs_array.zip
最適化すればvectorも速くなりますね。
配列には若干劣るようです。
・・・ちょっとまて!!
いやいや、ここまでの話は、
最適化すればなぜか配列のほうが優れた展開してくれるって話で、
vectorより配列のほうが優れてるって話ではない!!
そう!配列にvectorが負けたわけじゃない!!まだ終わってないんだ!!!
それにあれはただのシーケンシャルアクセスじゃないか!!
vectorと配列はランダムアクセスが定数時間で行える事が利点だろ!!
そこで勝負しようぜ!おん?
というわけで、ランダムアクセスでベンチマーク。
(srandによりrand関数が返す値の順番は保証され手いる事を利用し、1000万回ランダムアクセス。)
http://codepad.org/foS4inMs
最適化無し(-O0)
array: 4234 clock
vector:4484 clock
最適化あり(-O2)
array: 4062 clock
vector:4047 clock
ほらね!vectorと配列はあんま変わらないんだよ!
vector最強!今日から君もvectorするしかないね!
そしてvector使いが明かした真実に配列厨、顔面レッドスクリーンwwwwwwwww