88

gcc私はGoogleにオプションの意味を教えてくれるように頼みました-fomit-frame-pointer。それは私を以下のステートメントにリダイレクトします。

-fomit-frame-pointer

フレームポインタを必要としない関数のレジスタにフレームポインタを保持しないでください。これにより、フレームポインタを保存、設定、および復元する手順が回避されます。また、多くの機能で追加のレジスタを使用できるようにします。また、一部のマシンではデバッグが不可能になります。

各関数に関する私の知識によれば、すべてのローカル変数といくつかの詳細情報を保持するために、プロセスメモリのスタックにアクティベーションレコードが作成されます。このフレームポインタが関数のアクティベーションレコードのアドレスを意味することを願っています。

この場合、フレームポインタをレジスタに保持する必要のない関数の種類は何ですか?この情報を入手したら、(可能であれば)それに基づいて新しい関数を設計しようとします。フレームポインタがレジスタに保持されていない場合、一部の命令がバイナリで省略されるためです。これにより、多くの機能があるアプリケーションでパフォーマンスが大幅に向上します。

4

3 に答える 3

70

ほとんどの小さな関数はフレームポインタを必要としません-大きな関数はフレームポインタを必要とするかもしれません。

それは本当に、コンパイラがスタックの使用方法とスタックのどこにあるか(ローカル変数、現在の関数に渡される引数、および呼び出されようとしている関数のために準備されている引数)をどれだけうまく追跡できるかについてです。フレームポインタを必要とする、または必要としない関数を特徴づけるのは簡単ではないと思います(技術的には、フレームポインタを持つ関数はありません-コンパイラが複雑さを軽減する必要があると判断した場合はもっとそうです他のコード」)。

コーディング戦略の一環として「関数にフレームポインタを持たせないようにする」べきではないと思います。前述のように、単純な関数ではフレームポインタは必要ないので、を使用する-fomit-frame-pointerと、もう1つのレジスタが使用可能になります。レジスタアロケータの場合、関数への出入りに関する1〜3の命令を保存します。関数にフレームポインターが必要な場合、それはコンパイラーがフレームポインターを使用しないよりも優れたオプションであると判断したためです。フレームポインタのない関数を持つことは目標ではありません。正しくそして高速に動作するコードを持つことが目標です。

「フレームポインタがない」とパフォーマンスが向上するはずですが、大幅な改善をもたらす魔法の弾丸ではないことに注意してください。特に、最初から16個のレジスタがあるx86-64ではそうではありません。32ビットx86では、レジスタが8つしかないため、そのうちの1つはスタックポインタであり、もう1つをフレームポインタとして使用すると、レジスタスペースの25%が使用されます。これを12.5%に変更することは、かなりの改善です。もちろん、64ビット用にコンパイルすることも非常に役立ちます。

于 2013-02-02T21:29:47.403 に答える
27

これはすべて、IntelプラットフォームのBP / EBP/RBPレジスタに関するものです。このレジスタのデフォルトはスタックセグメントです(スタックセグメントにアクセスするために特別なプレフィックスは必要ありません)。

EBPは、スタック内のデータ構造、変数、および動的に割り当てられたワークスペースにアクセスするためのレジスターの最良の選択です。EBPは、現在のTOSではなく、スタック上の固定点を基準にしてスタック上の要素にアクセスするためによく使用されます。これは通常、現在のプロシージャ用に確立された現在のスタックフレームのベースアドレスを識別します。オフセット計算でEBPがベースレジスタとして使用される場合、オフセットは現在のスタックセグメント(つまり、SSによって現在選択されているセグメント)で自動的に計算されます。SSを明示的に指定する必要がないため、このような場合の命令エンコーディングはより効率的です。EBPを使用して、他のセグメントレジスタを介してアドレス指定可能なセグメントにインデックスを付けることもできます。

(ソース-http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm

ほとんどの32ビットプラットフォームでは、データセグメントとスタックセグメントが同じであるため、このEBP/RBPとスタックの関連付けは問題ではなくなりました。64ビットプラットフォームでも同様です。2003年にAMDによって導入されたx86-64アーキテクチャは、64ビットモードでのセグメンテーションのサポートを大幅に廃止しました。4つのセグメントレジスタ:CS、SS、DS、およびESは強制的に0になります。 。x8632ビットおよび64ビットプラットフォームのこれらの状況は、基本的に、メモリにアクセスするプロセッサ命令でEBP/RBPレジスタをプレフィックスなしで使用できることを意味します。

したがって、あなたが書いたコンパイラオプションを使用すると、BP / EBP / RBPを他の手段、たとえばローカル変数を保持するために使用できます。

「これにより、フレームポインタの保存、設定、および復元の手順が回避されます」とは、各関数のエントリで次のコードを回避することを意味します。

push ebp
mov ebp, esp

または、enterIntel80286および80386プロセッサで非常に役立つ命令。

また、関数が戻る前に、次のコードが使用されます。

mov esp, ebp
pop ebp 

またはleave指示。

デバッグツールは、スタックデータをスキャンし、これらのプッシュされたEBPレジスタデータを使用して、検索中にcall sites、つまり、関数の名前と引数を階層的に呼び出された順序で表示する場合があります。

プログラマーは、スタックフレームについて、広義ではなく(スタック内の単一のエンティティであり、1つの関数呼び出しのみを処理し、リターンアドレス、引数、およびローカル変数を保持する)、狭義の質問をする場合がありますstack frames。コンパイラオプションのコンテキスト。コンパイラーの観点からは、スタックフレームは、ルーチンの開始コードと終了コードにすぎず、アンカーをスタックにプッシュします。これは、デバッグや例外処理にも使用できます。デバッグツールは、スタックデータをスキャンし、これらのアンカーを使用してバックトレースを行いcall sites、スタック内に配置します。つまり、関数の名前を階層的に呼び出されたのと同じ順序で表示します。

そのため、コンパイラーは、コンパイラー・オプションの観点からスタック・フレームが何であるかを理解することが重要です。コンパイラーは、このコードを生成するかどうかを制御できるからです。

場合によっては、スタックフレーム(ルーチンの開始コードと終了コード)をコンパイラーが省略でき、変数は便利なベースポインター(BP /)ではなくスタックポインター(SP / ESP / RSP)を介して直接アクセスされます。 ESP / RSP)。コンパイラが一部の関数のスタックフレームを省略するための条件は異なる場合があります。たとえば、次のようになります。(1)関数がリーフ関数(つまり、他の関数を呼び出さないエンドエンティティ)。(2)例外は使用されません。(3)スタック上の発信パラメーターを使用してルーチンが呼び出されることはありません。(4)関数にはパラメーターがありません。

スタックフレーム(ルーチンの開始コードと終了コード)を省略すると、コードをより小さく、より高速にすることができます。それでも、スタックのデータをバックトレースしてプログラマーに表示するデバッガーの機能に悪影響を与える可能性もあります。これらは、コンパイラがスタックフレームの開始コードと終了コードを関数に与えるために関数が満たす必要がある条件を決定するコンパイラオプションです。たとえば、コンパイラには、次の場合に関数にそのような開始コードと終了コードを追加するオプションがあります:(a)常に、(b)決して、(c)必要な場合(条件を指定)。

一般性から特殊性に戻る:-fomit-frame-pointerGCCコンパイラオプションを使用する場合、ルーチンの開始コードと終了コードの両方、および追加のレジスタを使用することで勝つことができます(デフォルトでそれ自体または他のオプションによって暗黙的にオンになっている場合を除く)。この場合、EBP / RBPレジスタを使用することによるゲインの恩恵をすでに受けており、このオプションがすでに暗黙的にオンになっている場合は、このオプションを明示的に指定しても追加のゲインは得られません)。ただし、16ビットおよび32ビットモードでは、BPレジスタにはAXのように8ビット部分(ALおよびAH)へのアクセスを提供する機能がないことに注意してください。

このオプションは、コンパイラがEBPを最適化の汎用レジスタとして使用できるようにするだけでなく、スタックフレームの終了コードとエントリコードの生成を防ぎます。これにより、デバッグが複雑になります。そのため、GCCのドキュメントには明示的に記載されています(通常は太字で強調されています)。スタイル)このオプションを有効にすると、一部のマシンでデバッグが不可能になります

-fomit-frame-pointerまた、デバッグまたは最適化に関連する他のコンパイラオプションによって、オプションが暗黙的にオンまたはオフになる場合があることにも注意してください。

-fomit-frame-pointer 他のオプションがx86プラットフォームにどのように影響するかについての公式情報はgcc.gnu.orgで見つかりませんでした、https: //gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html次のことだけを述べています。

-Oは、デバッグに干渉しないマシンで-fomit-frame-pointerもオンにします。

したがって、x86プラットフォームで単一の「-O」オプションを使用してコンパイルした場合にオンになるかどうかは、ドキュメント自体からは明らかではありません。-fomit-frame-pointer経験的にテストされる可能性がありますが、この場合、GCC開発者は、将来このオプションの動作を予告なしに変更しないというコミットメントはありません。

ただし、Peter Cordes-fomit-frame-pointerはコメントで、x86-16プラットフォームとx86-32/64プラットフォームのデフォルト設定に違いがあることを指摘しています。

このオプション– –は、GCCだけでなく、IntelC++コンパイラ15.0-fomit-frame-pointerにも関連しています。

インテル®コンパイラーの場合、このオプションにはエイリアスがあり/Oyます。

Intelがそれについて書いたことは次のとおりです。

これらのオプションは、EBPが最適化の汎用レジスタとして使用されるかどうかを決定します。オプション-fomit-frame-pointerおよび/Oyは、この使用を許可します。オプション-fno-omit-frame-pointerおよび/Oy-はそれを許可しません。

一部のデバッガーは、EBPがスタックフレームポインターとして使用されることを想定しており、そうでない場合はスタックバックトレースを生成できません。-fno-omit-frame-pointerおよび/Oy-オプションは、デバッガーが次のことを行わなくてもスタックバックトレースを生成できるように、すべての関数のスタックフレームポインターとしてEBPを維持および使用するコードを生成するようにコンパイラーに指示します。

-fno-omit-frame-pointerの場合:-O0で最適化をオフにする/ Oy-の場合:/ O1、/ O2、または/ O3の最適化をオフにするオプション-を指定すると、-fno-omit-frame-pointerオプションが設定されます。 O0または-gオプション。-fomit-frame-pointerオプションは、オプション-O1、-O2、または-O3を指定したときに設定されます。

/ Oyオプションは、/ O1、/ O2、または/O3オプションを指定するときに設定されます。オプション/Oy-は、/Odオプションを指定するときに設定されます。

-fno-omit-frame-pointerまたは/Oy-オプションを使用すると、使用可能な汎用レジスターの数が1つ減り、コードの効率がわずかに低下する可能性があります。

注Linux*システムの場合:現在、GCC3.2の例外処理に問題があります。したがって、GCC3.2がC++用にインストールされ、例外処理がオンになっている場合(デフォルト)、インテル®コンパイラーはこのオプションを無視します。

上記の引用は、GCCではなくIntel C++15コンパイラにのみ関連することに注意してください。

于 2017-07-14T10:56:24.703 に答える
2

これまで「アクティベーションレコード」という用語に出くわしたことはありませんが、通常は「スタックフレーム」と呼ばれるものを指していると思います。これは、現在の関数によって使用されるスタック上の領域です。

フレームポインタは、現在の関数のスタックフレームのアドレスを保持するレジスタです。フレームポインタが使用されている場合、関数に入ると、古いフレームポインタがスタックに保存され、フレームポインタがスタックポインタに設定されます。関数を終了すると、古いフレームポインタが復元されます。

ほとんどの通常の関数は、独自の操作のためにフレームポインタを必要としません。コンパイラは、関数を介してすべてのコードパスのスタックポインタオフセットを追跡し、それに応じてローカル変数アクセスを生成できます。

フレームポインタは、デバッグや例外処理のコンテキストによっては重要な場合があります。最新のデバッグおよび例外処理形式は、ほとんどの場合、フレームポインターのない関数をサポートするように設計されているため、これはますますまれになっています。

最近フレームポインタが必要になるのは、関数がアロカ配列または可変長配列を使用する場合です。この場合、スタックポインタの値を静的に追跡することはできません。

于 2021-10-02T01:18:58.130 に答える