現: 2016-04-01 (金) 13:26:06 takatsuka[3] [4] | |||
---|---|---|---|
Line 1: | Line 1: | ||
+ | VC++には(それ以外のコンパイラにもきっと)、"エイリアスを使わないと仮定する"(/Ow,/Oa)という最適化オプションが用意されています。 | ||
+ | このオプションは、"サイズ優先"とか"実行速度優先"という最適化にしても有効にはならず、明示的に有効にしないといけないものなので、恩恵に預かっているケースは少ないと思います。 | ||
+ | |||
+ | まずリスト1に例を挙げます。上段がC++のソースコード。下段がコンパイラが出力したアセンブラコードです。 | ||
+ | |||
+ | -List1 | ||
+ | --ソース | ||
+ | #prettify{{ | ||
+ | void MemClear(int *pSize,char *pBuffer) | ||
+ | { | ||
+ | for(int i=0; i<*pSize; i++ ){ | ||
+ | pBuffer[i] = 0; | ||
+ | } | ||
+ | } | ||
+ | }} | ||
+ | --出力 | ||
+ | #prettify{{ | ||
+ | void MemClear(int *pSize,char *pBuffer) | ||
+ | { | ||
+ | mov ecx, pSize | ||
+ | xor eax, eax | ||
+ | cmp DWORD PTR [ecx], eax | ||
+ | jle SHORT LABEL2 | ||
+ | LABEL1 // ループ戻り先(このラベルは次のmovの下でいい気がする…) | ||
+ | mov edx,pBuffer // クリアするバッファを取得 | ||
+ | and BYTE PTR [eax+edx],0 // 1バイトクリア | ||
+ | inc eax // カウンタをインクリメント | ||
+ | cmp eax, DWORD PTR [ecx] // クリアサイズとカウンタを比較して | ||
+ | jl SHORT LABEL1 // まだ途中ならLABEL1に戻って繰り返す | ||
+ | LABEL2 | ||
+ | ret | ||
+ | } | ||
+ | }} | ||
+ | |||
+ | このコードは一見してわかるように、指定された領域のメモリをクリアする関数です。 | ||
+ | 出力されたコードをみると、コンパイラはとても素直に最適化されていないコードを吐き出していることがわかります。 | ||
+ | |||
+ | 今回の最適化オプションは、このような繰り返し処理を効率良いコードにする為の物です。リスト2。 | ||
+ | |||
+ | -List2 | ||
+ | --ソース | ||
+ | #prettify{{ | ||
+ | // "エイリアスを使わないと仮定する"を有効 | ||
+ | #pragma optimize ( "a", on ) | ||
+ | |||
+ | void MemClear(int *pSize,char *pBuffer) | ||
+ | { | ||
+ | for(int i=0; i<*pSize; i++ ){ | ||
+ | pBuffer[i] = 0; | ||
+ | } | ||
+ | } | ||
+ | #pragma optimize ( "", on ) // 元に戻す | ||
+ | }} | ||
+ | --出力 | ||
+ | #prettify{{ | ||
+ | void MemClear(int *pSize,char *pBuffer) | ||
+ | { | ||
+ | mov eax, pSize | ||
+ | mov ecx, DWORD PTR [eax] | ||
+ | test ecx, ecx | ||
+ | jle SHORT LABEL1 | ||
+ | mov edx, ecx | ||
+ | push edi | ||
+ | mov edi, pBuffer | ||
+ | xor eax, eax | ||
+ | shr ecx, 2 // クリアサイズ(の1/4。32BITで処理する為) | ||
+ | rep stosd // 一気にクリア | ||
+ | mov ecx, edx | ||
+ | and ecx, 3 // クリアサイズ(の4で割った余り) | ||
+ | rep stosb // 一気にクリア | ||
+ | pop edi | ||
+ | LABEL1 | ||
+ | ret | ||
+ | } | ||
+ | }} | ||
+ | |||
+ | リスト1と比べると、繰り返し処理(条件分岐)も無くなっており、見るからに早そうです。 | ||
+ | |||
+ | 本ネタで重要だと思うのは以下になります。 | ||
+ | |||
+ | なぜ、リスト1はリスト2のように最適化されないかというと、 | ||
+ | コンパイラは、繰り返しの条件であるクリアサイズ(*pSize)が、pBufferの示すバッファに値を書き込むことで、書き換えられてしまうかもしれない。 | ||
+ | と考えるからです。 | ||
+ | |||
+ | リスト2は、"エイリアスを使わないと仮定する"としているので、コンパイラは、pSizeとpBufferの領域がダブることはない。という前提があるので最適化されたコードを出力出来ます。 | ||
+ | |||
+ | ここまでの説明だと、"エイリアスを使わないと仮定する"を常に有効にすることで、何も考えずに今以上に最適化されるのではないか。と思ってしまうかもしれませんが、それは危険です。 | ||
+ | むやみやたらに最適化させると、ソースコード的には正しいけれど、その通りに動いてくれないコードが出力される可能性があります。 | ||
+ | |||
+ | |||
+ | 僕の個人的意見では、"エイリアスを使わないと仮定する"は無効にしたまま、ソースコード上で最適化される書き方をするという手段が吉だと思います。 | ||
+ | 例えばリスト3のようにします。 | ||
+ | |||
+ | -List3 | ||
+ | --ソース | ||
+ | #prettify{{ | ||
+ | void MemClear(int *pSize,char *pBuffer) | ||
+ | { | ||
+ | int nSize = *pSize; // サイズをローカル変数にコピー | ||
+ | for(int i=0; i<nSize; i++ ){ | ||
+ | pBuffer[i] = 0; | ||
+ | } | ||
+ | } | ||
+ | }} | ||
+ | --出力 | ||
+ | #prettify{{ | ||
+ | (リスト2と同じコードなので割愛) | ||
+ | }} | ||
+ | |||
+ | 最初にクリアサイズ(*pSize)をローカル変数にコピーして、そのローカル変数がforを抜ける条件であると書くことで、 | ||
+ | pBufferへの書き込みによってループ条件が変わることはないので、コンパイラは最適化されたコードを出力できます。 | ||
+ | |||
+ | |||
+ | 以上のことから、自分の個人的意見を言わせてもらうと、 | ||
+ | "エイリアスを使わないと仮定する"最適化オプションは使わないほうが吉。 | ||
+ | 使うことで最適化されるケースがあるのであれば、コードの書き方を工夫することで最適化するのが吉。 | ||
+ | |||
+ | "エイリアスを使わないと仮定する"最適化オプションの利用法としては、 | ||
+ | 無効の場合と有効の場合とで、出力されるアセンブラを比較して、 | ||
+ | もし有効にすることで最適化されるようなケースがあったなら、そこはまだコードの書き方を工夫できる。 | ||
+ | という判断材料として"エイリアスを使わないと仮定する"を使用するのが吉と思われます。 | ||
+ | |||
+ | /* | ||
+ | 最近のコンパイラは優秀だと改めて思いました。 | ||
+ | for文でメモリクリアって、昔は rep stosd とか使ってくれなかった気がします。 | ||
+ | 今回の例であるメモリクリアであれば memset とか使うのが常識なのですが、memsetだとBYTE値でしか埋めれないので、16Bit値や32Bit値で埋めたいときは for ループを使うしかないのですが、その場合でも rep stosd とか使ってくれるようになってました。 | ||
+ | |||
+ | なお、かといって memset を使わずに for でメモリクリアを実装しなきゃならないかというと、一概にはそう言えません。 | ||
+ | VC++(他のコンパイラは良く知りません)には、組み込み関数という仕組みがあって、memset は組み込み関数として用意されていますので、memset を使うことによるパフォーマンスの低下はまず無いでしょう。(組み込み関数を有効にする必要があります) | ||
+ | */ |
(This host) = https://thinkridge.com