私は Stack Overflow を検索して、関数のようなマクロとインライン関数の長所/短所を比較しました。
私は次の議論を見つけました: 異なるマクロ関数の長所と短所/ Cのインラインメソッド
...しかし、それは私の第一の燃える質問に答えませんでした。
つまり、メモリ使用量と実行速度の観点から、マクロ関数(変数、場合によっては他の関数呼び出しを伴う)とインライン関数を使用する場合のcのオーバーヘッドは何ですか?
オーバーヘッドにコンパイラ依存の違いはありますか?私は自由にiccとgccの両方を持っています。
私がモジュール化している私のコードスニペットは:
double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = AttractiveTerm * AttractiveTerm;
EnergyContribution +=
4 * Epsilon * (RepulsiveTerm - AttractiveTerm);
それをインライン関数/マクロに変換する理由は、それをcファイルにドロップしてから、条件付きで他の類似しているがわずかに異なる関数/マクロをコンパイルできるようにするためです。
例えば。:
double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = pow(SigmaSquared/RadialDistanceSquared,9);
EnergyContribution +=
4 * Epsilon * (RepulsiveTerm - AttractiveTerm);
(2行目の違いに注意してください...)
この関数は私のコードの中心的なものであり、プログラムのステップごとに数千回呼び出され、プログラムは数百万のステップを実行します。したがって、可能な限りオーバーヘッドを抑えたいので、インライン化とコードのマクロへの変換のオーバーヘッドを心配して時間を無駄にしているのはなぜですか。
以前の議論に基づいて、私はすでにマクロの他の長所/短所(タイプの独立性とそれから生じるエラー)を理解しています...しかし、私が最も知りたいこと、そして現在知らないことはパフォーマンスです。
Cベテランの何人かが私にとって素晴らしい洞察を持っていることを知っています!
インライン関数を呼び出すと、関数呼び出しが生成される場合と生成されない場合があり、通常は非常に少量のオーバーヘッドが発生します。 inline
関数が実際にインライン化される正確な状況は、コンパイラーによって異なります。ほとんどは小さな関数をインライン化するために誠実な努力をしますが(少なくとも最適化が有効になっている場合)、そうする必要はありません(C99、§6.7.4):
関数をインライン関数にすることは、関数の呼び出しが可能な限り高速であることを示唆しています。このような提案が効果的である範囲は、実装によって定義されます。
マクロは、そのようなオーバーヘッドが発生する可能性が低くなります(ただし、コンパイラが何らかの処理を行うのを防ぐことはほとんどありません。標準では、マシンコードプログラムの拡張対象を定義せず、コンパイルされたプログラムの監視可能な動作のみを定義します)。
よりクリーンなものを使用してください。プロフィール。問題がある場合は、別のことを行ってください。
また、fizzerが言ったこと; pow(および割り算)の呼び出しは、どちらも通常、関数呼び出しのオーバーヘッドよりも負荷が高くなります。それらを最小化することは良いスタートです:
double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += 4 * Epsilon * AttractiveTerm * (AttractiveTerm - 1.0);
EnergyContribution
は、このような用語のみで構成されていますか?その場合は、4 * Epsilon
出力し、反復ごとに2つの乗算を保存します。
double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += AttractiveTerm * (AttractiveTerm - 1.0);
// later, once you've done all of those terms...
EnergyContribution *= 4 * Epsilon;
マクロは実際には関数ではありません。マクロとして定義したものはすべて、コンパイラーがそれを見る前に、プリプロセッサーによって逐語的にコードにポストされます。プリプロセッサは、さまざまな抽象化を使用してコードをより適切に構造化できるソフトウェアエンジニアツールにすぎません。
インライン関数か、そうでなければコンパイラーは知っており、それをどうするかを決定できます。ユーザーがinline
キーワードを指定したのは単なる提案であり、コンパイラーがそれをオーバーライドする場合があります。ほとんどの場合、より優れたコードが得られるのはこのオーバーライドです。
コンパイラーが関数を認識していることのもう1つの副作用は、コンパイラーに特定の決定を強制する可能性があることです。たとえば、コードのインライン化を無効にすると、コードのデバッグやプロファイリングを改善できます。おそらくインライン関数がマクロに対して有効にする他の多くのユースケースがあります。
マクロは非常に強力ですが、これを裏付けるために、グーグルテストとグーグルモックを引用します。マクロを使用する理由はたくさんあります:D。
関数を使用して一緒にチェーンされる単純な数学演算は、特に関数が変換ステップで1回だけ呼び出される場合は特に、コンパイラーによってインライン化されることがよくあります。したがって、キーワードが指定されているかどうかに関係なく、コンパイラーがインライン決定を行うことは驚くに値しません。
ただし、コンパイラが対応していない場合は、コードのセグメントを手動でフラット化できます。平坦化すると、おそらくマクロが「抽象」として機能します。結局、マクロは「実際の」関数と同様のセマンティクスを提供します。
The Crux
それで、より良い物理コードを生成できるようにコンパイラーに特定の論理境界を認識させますか、それとも手動でまたはマクロを使用してコンパイラーをフラット化することにより、コンパイラーに強制決定を行いますか?業界は前者に傾いています。
この場合、マクロを使用することに傾倒します。それは、それが速くて汚いからです。それ以上学ぶ必要はありません。ただし、マクロはソフトウェアエンジニアリングの抽象概念であり、コンパイラーが生成するコードに関心があるため、問題が少し進んだ場合は、C++テンプレートを使用します。
除去したいpow()への呼び出しです。この関数は一般的な浮動小数点指数を取り、整数の指数に上げるには非効率的です。これらの呼び出しを、たとえば.
inline double cube(double x)
{
return x * x * x;
}
ここであなたのパフォーマンスに大きな違いをもたらす唯一のものです。
セキュリティとバグの喚起に関して、マクロとインライン関数について話しているCERT Secureコーディング標準を確認してください。次の理由により、関数のようなマクロの使用はお勧めしません。
関数のようなマクロを含むマクロは、単純なテキストの置換であり、reallyをパラメーターに注意しないと、お尻に食い込む可能性があります。たとえば、これまでになく人気のあるSQUAREマクロ:
_#define SQUARE(x) ((x)*(x))
_
SQUARE(i++)
として呼び出すと、発生するのを待っている災害になる可能性があります。また、関数のようなマクロにはスコープの概念がなく、ローカル変数をサポートしていません。最も人気のあるハックは次のようなものです
_#define MACRO(S,R,E,C) \
do \
{ \
double AttractiveTerm = pow((S)/(R),3); \
double RepulsiveTerm = AttractiveTerm * AttractiveTerm; \
(C) = 4 * (E) * (RepulsiveTerm - AttractiveTerm); \
} while(0)
_
もちろん、これはx = MACRO(a,b);
のような結果を割り当てることを難しくします。
正確性および保守性の観点からの最善策は、それを関数にしてinline
を指定することです。マクロは関数ではないため、それらと混同しないでください。
それが終わったら、パフォーマンスを測定し、ハッキングする前にactualのボトルネックがどこにあるかを見つけます(pow
の呼び出しは、合理化の候補になります)。
あなたの質問に答える最良の方法は、両方のアプローチをベンチマークして、yourアプリケーションでyourテストデータを使用して実際にどちらが速いかを確認することです。パフォーマンスに関する予測は、最も粗いレベルを除いて信頼できないことで有名です。
とはいえ、マクロとtrueインライン関数呼び出しの間に大きな違いはないと予想します。どちらの場合も、最終的には同じアセンブリコードを使用する必要があります。
random-pause thisの場合、おそらく表示されるのは、時間の100%(マイナスイプシロン)がpow
関数内にあるということです。基本的にいいえ違い。
あなたがそれを見つけたと仮定すると、最初にすべきことは、スタックで見つけたpow
への呼び出しを取り除くことです。 (一般的に、最初の引数のlog
を取り、2番目の引数を掛けて、そのexp
または同じことを行う何かを実行します。log
とexp
は、多くの算術演算を含むある種のシリーズで実行できます。もちろん、特殊なケースを探しますが、それでもなお、時間がかかります。)それだけで、桁違いのスピードアップです。
次に、ランダムポーズを再度実行します。今、あなたは多くの時間を費やしている何かを見ます。私はそれが何であるかを推測することはできませんし、他の誰もそうすることはできませんが、おそらくあなたもそれを減らすことができます。できなくなるまで続けてください。
これは、マクロの使用を選択する過程で発生する可能性があり、インライン関数よりもわずかに速い場合があります。それはあなたがそこに着いたときにあなたが判断することです。
他の人が言ったように、それは主にコンパイラに依存します。
"pow"のコストは、インライン化やマクロによる節約よりも高くなると思います:)
マクロではなくインライン関数の方がきれいだと思います。
キャッシュとパイプライン処理は、最新のプロセッサでこれを実行している場合、実際に大きな利益を得るところです。すなわち。 'if'のような分岐ステートメントを削除すると、大きな違いが生じます(いくつかのトリックで実行できます)
コンパイラーを書いている一部の人からは理解できますが、関数を内部から呼び出すと、コードがインライン展開される可能性はほとんどありません。しかし、それがマクロを使用してはならない理由です。マクロは情報を削除し、最適化するオプションをコンパイラーにはるかに残します。マルチパスコンパイラとプログラム全体の最適化により、コードをインライン化すると、分岐予測が失敗したり、キャッシュミスが発生したり、最新のCPUが高速で動作するために使用するその他の黒魔力が発生することがわかります。上記のコードはいずれにしても最適ではないことを誰もが指摘するのは正しいと思います。