全般的
通常のコンパイラでは、少なくとも通常のコンパイラを使用していると仮定した場合、生成されるコードはほとんどの場合同じになります。
csc.exe /optimize+
cl.exe /O2
g++ -O2
および関連するデフォルトの最適化モード。
一般的なマントラは、プロファイル、プロファイル、プロファイルです(そして、プロファイラーから指示されるまで、マイクロ最適化しないでください)。生成されたコード2をいつでも見て、改善の余地があるかどうかを確認できます。
このように考えてください。たとえば、C#コード:
C#/。NET
各complexExpressionsは、引数をスタックにプッシュする必要がある事実上の関数呼び出し呼び出し(call、calli、callvirt opcode 3 )です。戻り値は、終了時のパラメーターではなく、スタックにプッシュされたままになります。
現在、CLRはスタックベースの仮想マシン(つまりレジスタレス)であるため、これはスタック上の匿名の一時変数とまったく同じになります。唯一の違いは、コードで使用される識別子の数です。
JITエンジンがそれをどのように処理するかは別の問題です。JITエンジンはこれらの呼び出しをネイティブアセンブリに変換する必要があり、レジスタ割り当て、命令の順序付け、分岐予測などを微調整することで最適化を行う可能性があります1
1(実際には、このサンプルでは、complex function calls
副作用が発生する可能性があり、C#仕様が評価順序といわゆるシーケンスについて非常に明確であるため、より興味深い最適化を行うことはできません)。ただし、呼び出しのオーバーヘッドを減らすために、JITエンジンは関数呼び出しをインライン化できることに注意してください。
それらが非仮想である場合だけでなく、(IIRC)特定の.NETFramework内部のコンパイル時にランタイムタイプを静的に知ることができる場合もあります。これについてはリファレンスを調べる必要がありますが、実際には、フレームワーク関数のインライン化を明示的に防ぐために.NETFramework4.0で導入された属性があると思います。これは、ユーザーアセンブリが事前にネイティブイメージにコンパイルされている場合でも、Microsoftがサービスパック/更新プログラムのライブラリコードにパッチを適用できるようにするためです。
C / C ++
C / C ++では、メモリモデルははるかに緩く(つまり、少なくともC ++ 11まで)、コードは通常、コンパイル時に直接ネイティブ命令にコンパイルされます。C / C ++コンパイラは通常、積極的なインライン化を行うことを追加します。最適化を有効にせずにコンパイルしない限り、そのようなコンパイラでもコードは通常同じになります。
2私は使用します
ildasm
またはmonodis
生成されたILコードを確認するには
mono -aot=full,static
またはmkbundle
、ネイティブオブジェクトモジュールを生成しobjdump -CdS
、そのための注釈付きネイティブアセンブリ命令を表示します。
これは純粋に好奇心であることに注意してください。そのように興味深いボトルネックを見つけることはめったにないからです。ただし、ジェネリッククラス用に生成されたILコードに潜んでいる可能性のある驚きの良い例については、パフォーマンスの最適化に関するSkeetのブログ投稿のNoda.NET
Jを参照してください。
3コンパイラ組み込み関数の演算子の 編集は正確ではありませんが、結果はスタックに残されます。