メモリ ≫ -- プログラミング :: 高速なプログラムを書く為に :: コンパイラの最適化

5. アセンブリについて

「アセンブリ」と言うと、「高速なプログラム」の代名詞的な響きがありますが… これは余りお薦めしません。 最近のコンパイラは性能が高いので、自分でアセンブリコードを書いても太刀打ち出来ません。 下手に書くと絶対にコンパイラの吐くコードよりも遅くなりますし、 ちゃんと勉強してペアリングなども考慮して記述したとしても、 コンパイラの吐くコードと殆ど同じ速度のコードにしかなりません。

アセンブリで書いて効果があるのは、

位の物です。

どうしてもアセンブリで書きたいというのであれば、

  1. どんな命令が存在するのかをちゃんと知る。
  2. 実際に組む時には、各命令のクロック数を全て数え上げて短くなる様に勉める。

    勿論、ペアリングなども考慮する

  3. Intel の出している最適化マニュアルを読む

程度の事はしないと駄目だと思います。 やった事無いので本当にこれで速いコードが書けるのかどうかは分かりませんが。

まあ、速度の為ではなくて、 教養の為にアセンブリで書くというのならば、 アセンブリはそんなに難しくはないのですが…。

資料

資料は、Intel 日本語資料のダウンロードにあります。 日本語の資料があるというのはありがたい事です。興味ある人は是非読んでみると良いです。 見るべきは以下の物です。

一つ一つの命令に関しては、二番目のと三番目のに載っています。 アセンブリで何か書く時は、この二番目と三番目を片手に書く事になるでしょう。 速度を追求するのであれば五番目の物は必読です (僕は勿論読んでいません)。 各命令のクロック数も五番目のに載っていたと思います。 四番目のは、OS を作ってやるぜという人が読む為の物です。 上中下の下だけ仲間外れにするのも何なので載せましたが、読まなくて良いです。

世の中には、アセンブリの適当な入門サイトが色々あります。 然し、或る程度分かったら、結局の所 Intel のマニュアルが一番見易いと思います。 /* んー、C 言語の入門サイトは良い物が沢山あるのに、 アセンブリの入門サイトが余りないのは Intel の資料が完璧すぎるからなのかも。 */

6. コンパイラの最適化

最適化序でに、コンパイラの行う最適化についても覗いてみる事にしましょう。 茲では、VC++ による出力を見てみる事にします。

無意味な計算の省略

折角計算しても、その結果がプログラムの実行結果に影響を与えないのであれば、 元々計算する必要はありません。 コンパイラは、そう言った計算を消し去ってしまいます。

#include <stdio.h>

int main(){
	int a[10];
	for(int i=0;i<10;i++)a[i]=i*i;
	for(int i=0;i<10;i++)a[i]+=i;
	// ↑ 何か一生懸命計算して居るみたいだが…
	//    実行した結果は何処にも出力されない
	
	return 0;
}
;-------------------------------------------------------------------------------
; 最適化なし
;-------------------------------------------------------------------------------
00401000: push    ebp                        ; プロローグ
00401001: mov     ebp, esp                   ;
00401003: sub     esp, 30                    ;----------------------------------
00401006: mov     dword ptr ss:[ebp-2C], 0   ;   i = 0
0040100D: jmp     short 00401018             ;   goto 00401018
0040100F: mov     eax, ss:[ebp-2C]           ; +   eax =  i
00401012: add     eax, 1                     ; |   eax += 1
00401015: mov     ss:[ebp-2C], eax           ; |   i   =  eax
00401018: cmp     dword ptr ss:[ebp-2C], 0A  ; |   i と 10 を比較
0040101C: jge     short 0040102E             ; |   if(≧)goto 0040102E
0040101E: mov     ecx, ss:[ebp-2C]           ; |   ecx =  i
00401021: imul    ecx, ss:[ebp-2C]           ; |   ecx *= i
00401025: mov     edx, ss:[ebp-2C]           ; |   edx =  i
00401028: mov     ss:[ebp+edx*4-28], ecx     ; |   a[edx] = ecx
0040102C: jmp     short 0040100F             ; + goto 0040100F
0040102E: mov     dword ptr ss:[ebp-30], 0   ;   i = 0
00401035: jmp     short 00401040             ;   goto 00401040
00401037: mov     eax, ss:[ebp-30]           ; +   eax =  i
0040103A: add     eax, 1                     ; |   eax += 1
0040103D: mov     ss:[ebp-30], eax           ; |   i   =  eax
00401040: cmp     dword ptr ss:[ebp-30], 0A  ; |   i と 10 を比較
00401044: jge     short 00401059             ; |   if(≧)goto 00401059
00401046: mov     ecx, ss:[ebp-30]           ; |   ecx =  i
00401049: mov     edx, ss:[ebp+ecx*4-28]     ; |   edx = a[10]
0040104D: add     edx, ss:[ebp-30]           ; |   edx += i
00401050: mov     eax, ss:[ebp-30]           ; |   eax =  i
00401053: mov     ss:[ebp+eax*4-28], edx     ; |   a[eax] = edx
00401057: jmp     short 00401037             ; + goto 00401037
00401059: xor     eax, eax                   ;   戻り値 = 0
0040105B: mov     esp, ebp                   ;----------------------------------
0040105D: pop     ebp                        ; エピローグ
0040105E: ret                                ;
;-------------------------------------------------------------------------------
; 最適化あり
;-------------------------------------------------------------------------------
00401000: xor     eax, eax                   ; 戻り値 = 0
00401002: ret                                ; return

細かい事は分からなくても良いです。各行の意味はコメントに書いてあります。 プロローグとエピローグは、関数の呼出規約で定められた物です。

見て分かる様に、最適化する前ではちゃんと計算するコードが出力されていますが、 最適化を実行すると処理が綺麗に消えて無くなってしまいます。 計算しても計算結果が何処にも反映されていないからです。

定数伝播 (Constant propagation)

一番基本の最適化です。 定数の計算は初めから結果が分かっているので、 コンパイル時に可能な所まで実行する事が出来ます。

#include <stdio.h>

int main(){
	int a=10;
	int b=100;
	int c=a*b+a;

	printf("%d\n",c);
	return 0;
}
; コンパイル結果 (アセンブリ)
;-------------------------------------------------------------------------------
; 最適化無しの時の main 関数
;-------------------------------------------------------------------------------
00401000: push    ebp                       ; プロローグ
00401001: mov     ebp, esp                  ;
00401003: sub     esp, 0C                   ;-----------------------------------
00401006: mov     dword ptr ss:[ebp-4], 0A  ; a=10
0040100D: mov     dword ptr ss:[ebp-8], 64  ; b=100
00401014: mov     eax, ss:[ebp-4]           ; eax=a
00401017: imul    eax, ss:[ebp-8]           ; eax*=b
0040101B: add     eax, ss:[ebp-4]           ; eax+=a
0040101E: mov     ss:[ebp-C], eax           ; c=eax
00401021: mov     ecx, ss:[ebp-C]           ; ecx=c
00401024: push    ecx                       ; 引数2 = ecx
00401025: push    0040A150                  ; 引数1 = "%d\n" (数値は "%d\n" という文字列のアドレス)
0040102A: call    00401038                  ; call printf    (数値は printf のアドレス)
0040102F: add     esp, 8                    ;-----------------------------------
00401032: xor     eax, eax                  ; エピローグ
00401034: mov     esp, ebp                  ;
00401036: pop     ebp                       ;
00401037: ret                               ;
;-------------------------------------------------------------------------------
; 最適化ありの時の main 関数
;-------------------------------------------------------------------------------
00401000: push    3F2                       ; 引数2 = 1010
00401005: push    0040A150                  ; 引数1 = "%d\n"
0040100A: call    00401015                  ; call printf
0040100F: add     esp, 8                    ;-----------------------------------
00401012: xor     eax, eax                  ; エピローグ
00401014: ret                               ;

上を見れば分かる様に、最適化が有効になっていると、 いきなり 1010 という値が printf に渡されているという事が分かります。 (実は、一番強い最適化にしたら printf 関数まで inline 展開されて滅茶苦茶になったので、 ちょっと最適化を緩めています。)

条件分岐の確定

更に、定数伝播の結果、条件分岐が確定してしまう事もあります。 その場合には、行く事が出来ない分岐先は丸ごと消えて無くなります。

(一般に、到達不能と判定されたコードは全て消えて無くなります。)

#include <stdio.h>

int main(){
	int a=10;
	int b=100;
	if(a*10==b){
		printf("hello!");
	}else{
		printf("world!");
	}

	return 0;
}
;-------------------------------------------------------------------------------
; 最適化なし
;-------------------------------------------------------------------------------
00401000: push    ebp
00401001: mov     ebp, esp
00401003: sub     esp, 8
00401006: mov     dword ptr ss:[ebp-4], 0A
0040100D: mov     dword ptr ss:[ebp-8], 64
00401014: mov     eax, ss:[ebp-4]
00401017: imul    eax, eax, 0A
0040101A: cmp     eax, ss:[ebp-8]         ; eax==b かどうか
0040101D: jnz     short 0040102E          ; if(!equals)goto 0040102E
0040101F: push    0040A150                ;   "hello!"
00401024: call    00401041                ;   printf
00401029: add     esp, 4
0040102C: jmp     short 0040103B          ; goto 0040103B
0040102E: push    0040A158                ;   "world!"
00401033: call    00401041                ;   printf
00401038: add     esp, 4
0040103B: xor     eax, eax
0040103D: mov     esp, ebp
0040103F: pop     ebp
00401040: ret
;-------------------------------------------------------------------------------
; 最適化あり
;-------------------------------------------------------------------------------
00401000: push    0040A150                ; "hello!"
00401005: call    00401010                ; printf
0040100A: add     esp, 4
0040100D: xor     eax, eax
0040100F: ret

上のプログラムの場合 a*10==b という事が実行する前から分かるので、 if 文の真の節しかコンパイルされていません。 然も、a の中身や b の中身は他の場所で使われていないので、 結局整数に関する計算は全て無くなりました。

共通部分式括り出し

同じ部分式が一つの式の中に存在する時、(その式に副作用がなければ) 一回だけ計算してその結果を使い回す様に変更する事が可能です。

#include <stdio.h>

int main(){
	int s=0;
	for(int i=0;i<100;i++)
		s+=(s+i)*(s+i)*(s+i); // ← s+i が三回登場

	printf("%d\n",s);
	return 0;
}
00401000: xor     edx, edx           ; s = 0
00401002: xor     ecx, ecx           ; i = 0
00401004: push    esi
00401005: lea     eax, ds:[ecx+edx]  ;   eax=i+s先に s+i だけ計算しておく
00401008: mov     esi, eax           ;   esi=eax
0040100A: imul    esi, eax           ;   esi*=eax
0040100D: imul    esi, eax           ;   esi*=eax
00401010: inc     ecx                ;   i++;
00401011: add     edx, esi           ;   s+=esi
00401013: cmp     ecx, 64            ;   i と 100 を比較
00401016: jl      short 00401005     ; if(<)goto 00401005
00401018: push    edx                ; 引数2 = s
00401019: push    0040A150           ; 引数1 = "%d\n"
0040101E: call    0040102A           ; call printf
00401023: add     esp, 8
00401026: xor     eax, eax
00401028: pop     esi
00401029: ret

ループ不変量括り出し

ループの中で毎回同じ事を計算していると、 その部分はループの外に追い出されてしまいます。

#include <stdio.h>
#include <time.h>

int main(){
	int t=time(NULL)&0xFF;

	int s=0;
	for(int i=0;i<100;i++)
		s+=(s+i)*(t+5); // ← t+5 はループ中変化しない

	printf("%d\n",s);
	return 0;
}
00401010: push    0                 ; +
00401012: call    00401047          ; | eax=time(0)
00401017: add     esp, 4            ; +
0040101A: and     eax, 0FF          ; eax&=0xFF
0040101F: xor     edx, edx          ; s=0
00401021: xor     ecx, ecx          ; i=0
00401023: add     eax, 5            ; eax+=5 // ← ループ外で 5 を足している
00401026: push    esi               ;
00401027: lea     esi, ds:[ecx+edx] ; +   esi=i+s            
0040102A: imul    esi, eax          ; |   esi*=eax           
0040102D: inc     ecx               ; |   i++                
0040102E: add     edx, esi          ; |   s+=esi             
00401030: cmp     ecx, 64           ; |   i と 100           
00401033: jl      short 00401027    ; + if(<)goto 00401027
00401035: push    edx
00401036: push    0040A150          ; "%d\n"
0040103B: call    00401098          ; printf
00401040: add     esp, 8
00401043: xor     eax, eax
00401045: pop     esi
00401046: ret

分岐削除

条件分岐は、前に述べた様に分岐予測失敗の可能性があるので、出来るだけ少ない方がよいです。

分岐の結合

同じ条件による分岐が存在したら、それをくっつける事が可能です。

#include <stdio.h>
#include <time.h>

int main(){
	int t=time(NULL)&1;

	int a=t==0?t+4 :t-5; // ← 分岐1
	int b=t!=0?t+10:t-5; // ← 分岐2

	printf("%d\n",a+b);
	return 0;
}
00401010: push    0               ;┐          0
00401012: call    00401055h       ;│ eax=time( );
00401017: add     esp,4           ;┘ 
0040101A: and     eax,1           ;   eax&=1;
0040101D: jne     0040103Ch       ;   if(eax==0){ // ← 分岐
0040101F: mov     ecx,4           ;       ecx= 4;
00401024: mov     eax,0FFFFFFFBh  ;       eax=-5;
00401029: add     eax,ecx         ;       eax+=ecx;
0040102B: push    eax             ;┐                   eax
0040102C: push    0040A150h       ;│            "%d\n"    
00401031: call    004010A6h       ;│     printf(      ,   );
00401036: add     esp,8           ;┘
00401039: xor     eax,eax         ;┐            0
0040103B: ret                     ;┘     return  ;
                                  ;   }else{
0040103C: lea     ecx,[eax-5]     ;       ecx=eax-5;
0040103F: add     eax,0Ah         ;       eax=10;
00401042: add     eax,ecx         ;       eax+=ecx;
00401044: push    eax             ;┐                   eax
00401045: push    0040A150h       ;│            "%d\n"    
0040104A: call    004010A6h       ;│     printf(      ,   );
0040104F: add     esp,8           ;┘
00401052: xor     eax,eax         ;┐            0
00401054: ret                     ;┘     return  ;
                                  ;   }

ループ展開 (Loop Unrolling)

ループを部分的に展開する事によって、条件分岐の回数を少なくする事が出来ます。 更に、各ループ間の最適化を実行できるようになると言う期待も出来ます。

#include <stdio.h>

int main(){
	int s=0;
	for(int i=0;i<100;i++)s+=i;
	printf("%d\n",s);
	return 0;
}
00401000: push    esi
00401001: push    edi
00401002: xor     edi,edi         ;   s=0;
00401004: xor     eax,eax         ;   i=0;
00401006: xor     esi,esi         ;   s3=0;
00401008: xor     edx,edx         ;   s2=0;
0040100A: xor     ecx,ecx         ;   s1=0;
0040100C: lea     esp,[esp]       ;   esp=esp; /* ...何がしたいんだ?? */
                                  ;   do{
00401010: add     edi,eax         ;      s+=i;
00401012: lea     ecx,[ecx+eax+1] ;      s1+=i+1;
00401016: lea     edx,[edx+eax+2] ;      s2+=i+2;
0040101A: lea     esi,[esi+eax+3] ;      s3+=i+3;
0040101E: add     eax,4           ;      i+=4;
00401021: cmp     eax,64h         ;┐        eax  100
00401024: jl      00401010        ;┘ }while(   <=   );
00401026: add     esi,edx         ;   s3+=s2;
00401028: add     esi,ecx         ;   s3+=s1;
0040102A: add     edi,esi         ;   s+=s3;
0040102C: push    edi
0040102D: push    0040A150
00401032: call    0040103F
00401037: add     esp,8
0040103A: pop     edi
0040103B: xor     eax,eax
0040103D: pop     esi
0040103E: ret
// 要するに ↓ の様な感じに変換された
//  条件分岐判定回数は 25 回に減る
#include <stdio.h>

int main(){
	int s0=0,s1=0,s2=0,s3=0;
	for(int i=0;i<100;i+=4){
		s0+=i;
		s1+=i+1;
		s2+=i+2;
		s3+=i+3;
	}
	printf("%d\n",s0+s1+s2+s3);
	return 0;
}

インライン展開 (Inline Expansion)

インライン展開は、関数呼出の代わりに呼び出したい関数の中身をその場で評価するという物です。 インライン化すると、

という利点があります。

※ 然し、欠点も存在します。 例えば、巨大な関数を inline 展開してもコードサイズの肥大化を招き、 プログラム自体がキャッシュミスしてしまう可能性が高くなります。 また、巨大でなくても色々な所から呼び出されている関数をインライン展開してしまうと、 呼出毎に関数のコピーが出来てしまい、これもコードサイズの肥大化を招きます。 (インライン化した方が良いか、しない方が良いかというのは、コンパイラが判断してくれます。)

#include <stdio.h>

int f(int i){
	if(i==1){
		return 10;
	}else{
		return 20;
	}
}

int main(){
	printf("%d\n",f(1));
	return 0;
}
00401010: push   0Ah       ;┐               10
00401012: push   0040A150h ;│        "%d\n"   
00401017: call   00401022h ;│ printf(      ,  );
0040101C: add    esp,8     ;┘
0040101F: xor    eax,eax   ;┐        0
00401021: ret              ;┘ return  ;

上記では、f の中身が main の中に展開され、 更に、条件分岐が確定して結果の 10 が直接 printf に渡されています。

inline キーワード

最適化する設定にしていると inline というキーワードは余り意味を為さなくなります。 インライン展開した方が良いか、インライン展開をしない方が良いかというのは全てコンパイラが判断する為です。 なので、巨大な関数に inline を指定してもインライン展開してはくれませんし、 軽量な関数であれば inline を指定しなくても勝手にインライン化してくれます。

従って、inline は単にオブジェクトファイルに関数実体を含めるか含めないか、 という事を指定するキーワードと思うと良いです。

代替演算に置き換え

或る命令を実行する代わりに、別の命令を使って等価な結果を得る事が出来る場合があります。 より少ない命令数、 より少ないクロック数、 より少ない命令語長 (命令の長さは可変です。) で結果が得られる場合には、 代替命令を使って計算が実現されます。

#include <stdio.h>

volatile unsigned t=15;

int main(){
	printf("%u\n",t*1024);
	printf("%u\n",t/4);
	printf("%u\n",t*2);
	printf("%u\n",t*3);
	printf("%u\n",123+t*4);

	return 0;
}
00401000: mov     eax,dword ptr ds:[0040C000h] ;   eax=t;
00401005: shl     eax,0Ah                      ;   eax<<=10;
00401008: push    eax                          ;┐               eax
00401009: push    40A150h                      ;│        "%u\n"    
0040100E: call    0040106B                     ;┘ printf(      ,   );
00401013: mov     ecx,dword ptr ds:[0040C000h] ;   ecx=t;
00401019: shr     ecx,2                        ;   ecx>>=2;
0040101C: push    ecx                          ;┐               ecx
0040101D: push    40A154h                      ;│        "%u\n"    
00401022: call    0040106B                     ;┘ printf(      ,   );
00401027: mov     edx,dword ptr ds:[0040C000h] ;   edx=t;
0040102D: add     edx,edx                      ;   edx+=edx;
0040102F: push    edx                          ;┐               edx
00401030: push    40A158h                      ;│        "%u\n"    
00401035: call    0040106B                     ;┘ printf(      ,   );
0040103A: mov     eax,dword ptr ds:[0040C000h] ;   eax=t;
0040103F: lea     eax,[eax+eax*2]              ;   eax=eax+2*eax;
00401042: push    eax                          ;┐               eax
00401043: push    40A15Ch                      ;│        "%u\n"    
00401048: call    0040106B                     ;┘ printf(      ,   );
0040104D: mov     ecx,dword ptr ds:[0040C000h] ;   ecx=t;
00401053: lea     edx,[ecx*4+0000007Bh]        ;   edx=ecx*4+123;
0040105A: push    edx                          ;┐               edx
0040105B: push    40A160h                      ;│        "%u\n"    
00401060: call    0040106B                     ;┘ printf(      ,   );
00401065: add     esp,28h                      ;   (引数後始末)
00401068: xor     eax,eax                      ;   eax^=eax;
0040106A: ret                                  ;   return eax;

戻り値最適化 (Return Value Optimization; RVO)

これは、一定の大きさ以上の型を戻り値にした関数呼出に関する最適化です。

struct P{int x,y,z;}; // 12B の型
P f(int n){
	P ret={0};
	ret.x=n*10;
	ret.y=n*100;
	return ret;
}

P g();
int h(){
	P val=g();
	return val.x+val.z;
}
;-------------------------------------------------------------------------------
; 最適化なし
;-------------------------------------------------------------------------------
; struct P __cdecl f(int n);
00401000: push    ebp                     ;
00401001: mov     ebp,esp                 ;
00401003: sub     esp,0Ch                 ;
00401006: mov     dword ptr [ebp-0Ch],0   ; ret.x=0;      ┐
0040100D: xor     eax,eax                 ; eax=0;        │
0040100F: mov     dword ptr [ebp-8],eax   ; ret.y=eax;    │
00401012: mov     dword ptr [ebp-4],eax   ; ret.z=eax;    ┘ ret={0};
00401015: mov     ecx,dword ptr [ebp+0Ch] ; ecx=n;
00401018: imul    ecx,ecx,0Ah             ; ecx*=10;
0040101B: mov     dword ptr [ebp-0Ch],ecx ; ret.x=ecx;
0040101E: mov     edx,dword ptr [ebp+0Ch] ; edx=n;
00401021: imul    edx,edx,64h             ; edx*=100;
00401024: mov     dword ptr [ebp-8],edx   ; ret.y=edx;
00401027: mov     eax,dword ptr [ebp+8]   ; eax=_ret;     ┐
0040102A: mov     ecx,dword ptr [ebp-0Ch] ; ecx=ret.x;    │
0040102D: mov     dword ptr [eax],ecx     ; eax->x=ecx;   │
0040102F: mov     edx,dword ptr [ebp-8]   ; edx=ret.y;    │
00401032: mov     dword ptr [eax+4],edx   ; eax->y=edx;   │
00401035: mov     ecx,dword ptr [ebp-4]   ; ecx=ret.z;    │ // 戻り値書き込み先にコピー
00401038: mov     dword ptr [eax+8],ecx   ; eax->z=ecx;   ┘ *_ret=ret;
0040103B: mov     eax,dword ptr [ebp+8]   ; eax=_ret;
0040103E: mov     esp,ebp 
00401040: pop     ebp
00401041: ret
; int __cdecl h(void)
00000050: push    ebp
00000051: mov     ebp,esp
00000053: sub     esp,28h                 ;   P _ret; int padding; P tmp,val;
00000056: lea     eax,[ebp-28h]           ;   eax=&_ret; // _ret は戻り値書き込み先	
00000059: push    eax                     ;┐   eax
0000005A: call    ?g@@YA?AUP@@XZ          ;│ g(   );
0000005F: add     esp,4                   ;┘ 
00000062: mov     ecx,dword ptr [eax]     ;┐               ┐
00000064: mov     dword ptr [ebp-18h],ecx ;┘ tmp.x=eax->x; │
00000067: mov     edx,dword ptr [eax+4]   ;┐               │
0000006A: mov     dword ptr [ebp-14h],edx ;┘ tmp.y=eax->y; │
0000006D: mov     eax,dword ptr [eax+8]   ;┐               │
00000070: mov     dword ptr [ebp-10h],eax ;┘ tmp.z=eax->z; ┘ tmp=(戻り値); コピー
00000073: mov     ecx,dword ptr [ebp-18h] ;┐               ┐
00000076: mov     dword ptr [ebp-0Ch],ecx ;┘ val.x=tmp.x;  │
00000079: mov     edx,dword ptr [ebp-14h] ;┐               │
0000007C: mov     dword ptr [ebp-8],edx   ;┘ val.y=tmp.y;  │
0000007F: mov     eax,dword ptr [ebp-10h] ;┐               │
00000082: mov     dword ptr [ebp-4],eax   ;┘ val.z=tmp.z;  ┘ val=tmp; コピー
00000085: mov     eax,dword ptr [ebp-0Ch] ;   eax=val.x;
00000088: add     eax,dword ptr [ebp-4]   ;   eax+=val.z;
0000008B: mov     esp,ebp
0000008D: pop     ebp
0000008E: ret

上に挙げているのは、最適化していない状態でのコードです。

無駄にコピーを頻発している事が分かると思います。 戻り値最適化ではこのコピーを省略します。 (C++ 言語レベルで operator= の呼出を削除してしまいます。)

;-------------------------------------------------------------------------------
; 最適化あり
;-------------------------------------------------------------------------------
; struct P __cdecl f(int n);
00000000: mov     eax,dword ptr [esp+4]   ; eax=_ret;
00000004: xor     ecx,ecx                 ; ecx=0;
00000006: mov     dword ptr [eax+8],ecx   ; eax->z=0;   // 戻り値書き込み先に直接書込
00000009: mov     ecx,dword ptr [esp+8]   ; ecx=n;
0000000D: lea     edx,[ecx+ecx*4]         ; edx=ecx+ecx*4;
00000010: imul    ecx,ecx,64h             ; ecx*=100;
00000013: add     edx,edx                 ; edx+=edx;
00000015: mov     dword ptr [eax],edx     ; eax->x=edx; // 戻り値書き込み先に直接書込
00000017: mov     dword ptr [eax+4],ecx   ; eax->y=ecx; // 戻り値書き込み先に直接書込
0000001A: ret
; int __cdecl h(void)
00000020: sub     esp,18h                 ;   P val,???;
00000023: lea     eax,[esp]               ;   eax=&val;
00000026: push    eax                     ;┐   eax   // 戻り値書き込み先として val を直接指定
00000027: call    ?g@@YA?AUP@@XZ          ;┘ g(   );
0000002C: mov     edx,dword ptr [eax+4]   ;   edx=eax-&y;
0000002F: mov     ecx,dword ptr [eax]     ;   ecx=eax-&x;
00000031: mov     eax,dword ptr [eax+8]   ;   eax=eax-&z;
00000034: mov     dword ptr [esp+14h],edx ;   ???=edx;
00000038: add     eax,ecx                 ;   eax+=ecx;
0000003A: add     esp,1Ch
0000003D: ret

メモリ ≫ -- プログラミング :: 高速なプログラムを書く為に :: コンパイラの最適化
起稿 2010-02-15
© 2010, K. Murase myoga.murase@gmail.com
inserted by FC2 system