web-dev-qa-db-ja.com

値渡しと参照渡しまたはポインタ渡しのパフォーマンスコスト

オブジェクトfoointdouble、カスタムstructclassなど)を考えてみましょう。私の理解では、関数への参照によってfooを渡す(または単にfooへのポインタを渡す)と、ローカルコピー(fooは大きい)。

しかし、答えから ここ 実際には、64ビットシステム上のポインターは、何がポイントされているかに関係なく、8バイトのサイズを持つと予想されるようです。私のシステムでは、floatは4バイトです。つまり、foofloat型の場合、fooポインタを与えるのではなく、値によって(関数内で他のものよりも効率的に使用できる他の制約がないと仮定して)?

23
space_voyager

「コスト」の意味と、運用に関するホストシステムのプロパティ(ハードウェア、オペレーティングシステム)に依存します。

コスト測定値がメモリ使用量である場合、コストの計算は明らかです-コピーされるもののサイズを合計します。

測定が実行速度(または「効率」)である場合、ゲームは異なります。ハードウェア(およびオペレーティングシステムとコンパイラ)は、専用回路(マシンレジスタ、およびその使用方法)により、特定のサイズのものをコピーする操作のパフォーマンスに対して最適化される傾向があります。

たとえば、マシンが「スイートスポット」になるアーキテクチャ(マシンレジスタ、メモリアーキテクチャなど)を持つのが一般的です-あるサイズの変数をコピーするのが最も「効率的」ですが、より大きいOR SMALLER変数はそれほど小さくありません。小さな変数を複数コピーする必要があるため、大きな変数はコピーに多くの費用がかかります。小さな変数は小さな値をコピーする必要があるため、小さな変数も高くなります。より大きな変数(またはレジスタ)に入れて、それに対して操作を行ってから、値をコピーして戻します。

浮動小数点を使用する例には、ネイティブに倍精度浮動小数点(C++のdouble)をネイティブにサポートするいくつかのクレイスーパーコンピューターが含まれ、単精度(C++のfloat)のすべての操作はソフトウェアでエミュレートされます。一部の古い32ビットx86 CPUも32ビット整数で内部的に動作し、16ビット整数での操作は32ビットとの間の変換のためにより多くのクロックサイクルを必要としました(これは最新の32ビットまたは64では当てはまりません) 16ビット整数を32ビットレジスタにコピーしたり、32ビットレジスタからコピーしたり、それらのペナルティを減らして操作したりできるため、ビットx86プロセッサ。

非常に大きな構造を値でコピーすると、そのアドレスを作成してコピーするよりも効率が悪くなるのは簡単です。しかし、上記のような要因により、「値によってそのサイズの何かをコピーするのが最善」と「アドレスを渡すのが最善」のクロスオーバーポイントはあまり明確ではありません。

ポインターと参照は同様の方法で実装される傾向があります(たとえば、参照渡しは、ポインターを渡すのと同じ方法で実装できます)が、保証されていません。

確認する唯一の方法は、それを測定することです。また、測定値はシステムによって異なることを理解してください。

19
Peter

誰も言及していないことが1つあります。

「参照による受け渡し」を「値による受け渡し」に自動的に置き換えるIPA SRAと呼ばれる特定のGCC最適化があります。 https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html =(-fipa-sra)

これはほとんどの場合、デフォルト以外のコピーセマンティクスを持たず、CPUレジスタに収まるスカラータイプ(int、doubleなど)で行われます。

これにより

void(const int &f)

おそらく高速(およびスペース最適化)

void(int f)

したがって、この最適化を有効にすると、小さな型の参照の使用は、値で渡すのと同じくらい高速になります。

一方、(たとえば)std :: stringを値で渡すことは、カスタムコピーセマンティクスが関係しているため、参照による速度に最適化できませんでした。

私が理解していることから、すべてに参照渡しを使用すると、値で渡すものと参照で渡すものを手動で選択するよりも遅くなることはありません。

これは、特にテンプレートの場合に非常に便利です。

template<class T>
void f(const T&)
{
    // Something
}

常に最適です

10
peku33

パフォーマンスが絶対に重要である特定のシナリオをテストする必要がありますが、コンパイラーに特定の方法でコードを生成させようとすることに非常に注意してください。

コンパイラのオプティマイザーは、最終結果が証明可能な限り同じであれば、任意の方法でコードを書き換えることができます。これにより、非常に優れた最適化が可能になります。

Floatを値で渡すには、floatのコピーを作成する必要がありますが、適切な条件下では、floatを参照で渡すと、CPU浮動小数点レジスタに元のfloatを格納でき、そのレジスタを「参照」パラメータとして扱うことができます関数に。対照的に、コピーを渡す場合、コンパイラはレジスタの内容を保存するためにコピーを保存する場所を見つける必要があります。さらに悪いことに、必要性のためにレジスタをまったく使用できない場合がありますオリジナルを保存します(これは特に再帰関数に当てはまります!)。

この違いは、インライン化できる関数に参照を渡す場合にも重要です。コンパイラはコピーされたパラメーターが元のパラメーターを変更できないことを保証する必要がないため、参照によってインライン化のコストを削減できます。

言語では、やりたいことよりもやりたいことの記述に集中できるほど、コンパイラはあなたのために創造的な方法でハードワークを行うことができます。特にC++では、パフォーマンスを気にせず、代わりに、できるだけ明確に、簡単に必要なものを記述することに集中するのが一般的に最善です。あなたがどのように仕事をしたいかを説明しようとすることで、あなたのコードを最適化するという仕事をコンパイラがするのを防ぐことができます。

4
Matt Jordan

つまり、fooがfloat型の場合、fooを値で渡す方が効率的ですか?

値でフロートを渡す方が効率的です。私はそれがより効率的であることを期待しています-あなたが言ったことのために部分的に:フロートはあなたが記述するシステム上のポインタよりも小さいです。しかし、さらに、ポインターをコピーするとき、関数内で値を取得するためにポインターを間接参照する必要があります。ポインターによって追加されたインダイレクションは、パフォーマンスに大きな影響を与える可能性があります。

効率の違いは無視できます。特に、関数をインライン化でき、最適化が有効になっている場合、違いはほとんどありません。

あなたのケースで測定することで値でフロートを渡すことからパフォーマンスの向上があるかどうかを知ることができます。プロファイリングツールを使用して効率を測定できます。

ポインタを参照に置き換えることもできますが、答えは引き続き同じように適用されます。

参照を使用する際に何らかのオーバーヘッドがありますか、ポインターを逆参照する必要がある場合の方法ですか?

はい。参照は、ポインタとまったく同じパフォーマンス特性を持っている可能性があります。参照またはポインターのいずれかを使用して意味的に同等のプログラムを作成できる場合、おそらく両方が同一のアセンブリを生成します。


小さなオブジェクトをポインタで渡す方がコピーするよりも速い場合、同じサイズのオブジェクトの場合は間違いないでしょう、同意しませんか?ポインターへのポインターはどうですか、ポインターのサイズですよね? (まったく同じサイズです。)ああ、しかしポインターもオブジェクトです。そのため、オブジェクト(ポインターなど)をポインターごとに渡す方がオブジェクト(ポインター)をコピーするよりも高速である場合、ポインターへのポインターへのポインターへのポインター...ポインターを使用しなかったポインターよりも高速なポインターが少なくなります...おそらく、ここで無限の効率のソースが見つかりました:)

3
eerorika