1

状態のないファンクタークラスがあるが、新しいものを使用してヒープから作成する場合、一般的なコンパイラーは、作成のオーバーヘッドを完全に最適化するのに十分スマートですか?

この質問は、ステートレスなファンクターをたくさん作成するときに出てきました。それらがスタックに割り当てられている場合、それらの0状態のクラス本体は、スタックが実際にはまったく変更されていないことを意味しますか?後でファンクターインスタンスのアドレスを取得する場合に備えて、それが必要なようです。ヒープ割り当てについても同じです。

その場合、ファンクターは常に(些細な、しかしゼロではない)オーバーヘッドを作成に追加します。しかし、コンパイラはそのアドレスが使用されているかどうかを確認でき、使用されていない場合はそのスタック割り当てを排除できる可能性があります。(または、ヒープ割り当てを削除することもできますか?)

しかし、一時的に作成されたファンクターはどうですか?

#include <iostream>

struct GTfunctor 
{
  inline bool operator()(int a, int b) {return a>b; }
};

int main()
{
  GTfunctor* f= new GTfunctor;
  GTfunctor g;

  std::cout<< (*f)(2,1) << std::endl;
  std::cout<< g(2,1) << std::endl;
  std::cout<< GTfunctor()(2,1) << std::endl;
  delete f;
}

したがって、上記の具体的な例では、3つの行はそれぞれ3つの異なる方法で同じファンクターを呼び出します。この例では、方法の間に効率の違いはありますか?または、コンパイラーは、計算のない印刷ステートメントになるまで、各行を最適化できますか?

編集:ほとんどの回答は、コンパイラがヒープに割り当てられたファンクターをインライン化/削除することはできないと言っています。しかし、これも本当に本当ですか?ほとんどのコンパイラ(GCC、MS、Intel)には、リンクタイムの最適化もあり、実際にこの最適化を行うことができます。(しかし、そうですか?)

4

9 に答える 9

3

典型的なコンパイラは、作成のオーバーヘッドを完全に最適化するのに十分賢いですか?

ヒープ上でそれらを作成しているとき、コンパイラーが許可されているかどうかは疑問です。IMO:

  • newを呼び出すことは、演算子newを呼び出すことを意味します。
  • 演算子newは、ランタイムライブラリで定義されている重要な関数です。
  • コンパイラーは、そのような関数を実際に呼び出すつもりはなかったと判断したり、最適化としてサイレントに呼び出さないことを判断したりすることはできません。

スタック上にそれらを作成していて、それらのアドレスを取得していない場合、多分...またはそうではないかもしれません:私の推測では、すべてのオブジェクトは、メモリを占有するために、ゼロ以外のサイズを持っています。オブジェクトにID以外の状態がない場合でも、ID。

于 2009-05-11T02:07:54.640 に答える
3

明らかに、それはあなたのコンパイラに依存します。

私は言うだろう

  • ヒープ上のオブジェクトを最適化するコンパイラはありません。(これは、ChrisWが言うように、コンパイラーがnewの呼び出しを最適化することは決してないためです。これは、ほぼ確実に別の変換単位で定義されます。)

  • 一部のコンパイラは、スタック上の名前付きオブジェクトを最適化します。私はgccがこの最適化を頻繁に行うことを知っています。

  • ほとんどのコンパイラは、スタック上の名前のないオブジェクトを最適化します。これは「標準」のC++最適化の1つであり、特に上級のC ++ユーザーは、名前のない一時変数を多数作成する傾向があるためです。

残念ながら、これらは経験則にすぎません。オプティマイザは予測できないことで有名です。コンパイラが何をしているかを知る唯一の方法は、アセンブリ出力を読み取ることです。

于 2009-05-11T02:46:52.377 に答える
1

このタイプの最適化が許可されているかどうかは非常に疑わしいですが、ファンクターに状態がない場合、なぜそれをヒープで初期化したいのでしょうか? 一時的なものと同じくらい使いやすいはずです。

于 2009-05-11T02:31:06.053 に答える
1

C++ オブジェクトのサイズは常にゼロではありません。「空の基本クラスの最適化」では、空の基本クラスのサイズをゼロにすることができますが、ここでは当てはまりません。

私は C++ オプティマイザーに取り組んだことがないので、私が言うことはただの憶測です。2ndと3rdは簡単にインライン展開してオーバーヘッドも無く、GTFunctorも作らないと思います。ただし、ファンクター ポインターは別の話です。あなたの例では、それは十分に単純に見えるかもしれませんし、どのオプティマイザもヒープ割り当てを排除できるはずですが、重要なプログラムでは、ある翻訳単位でファンクターを作成し、別の翻訳単位で使用する可能性があります。または、コンパイラ/リンカー/ローダー/ランタイム システムにソース コードがなく、最適化がほとんど不可能な別のライブラリでも。最適化が容易ではないという事実を考えると、潜在的なパフォーマンスの向上は大きくなく、空のファンクターがヒープに割り当てられるケースの数はおそらく少ないでしょう。

于 2009-05-11T02:38:09.647 に答える
1

コンパイラは、new または delete の呼び出しを最適化できません。ただし、スタック上に作成された変数は状態を持たないため、最適化される可能性があります。

于 2009-05-11T02:38:41.487 に答える
1

ヒープの質問に答える簡単な方法:

GTfunctor *f = new GTfunctor;

の値はfnull であってはなりません。そして、あなたも持っていました:

GTfunctor *g = new GTfunctor;

の値は のg値と同じであってはなりませんf

さらに、どこかのポインタが何らかの方法でorに等しいように初期化されていない限り、 forgは から取得した他のポインタと等しくない可能性があります。これには (後に続くコードに応じて) プログラムの残りの部分全体が何をするかを調べる必要があります。newfg

はい、コードのローカル検査によって、コンパイラーがこれらの要件のいずれにも依存していないことがわかる場合、ヒープ割り当てが発生しないように書き換えを実行できます。問題は、あなたのコードがそれほど単純な場合、おそらく自分で書き直して、とにかく読みやすいプログラムになる可能性があることです.たとえば、テストプログラムはスタックベースのg例のようになります. したがって、実際のプログラムは、コンパイラでのこのような最適化の恩恵を受けません。

おそらく、これを行っている理由は、実行時に選択された型に応じて、ファンクターにデータがある場合があるためです。したがって、コンパイル時の分析は、ここでは魔法のようにうまく機能しません。

于 2009-05-11T11:08:26.223 に答える
1

C++ 標準では、各オブジェクト (ヒープ上の imho) には少なくとも 1 バイトのサイズが必要であるため、一意にアドレス指定できると規定されています。

new でファンクターを生成すると、次の 2 つの問題が発生する可能性があります。

  1. 構造は通常、最適化して取り除くことはできません。New は、複雑な副作用 (bad_alloc) を持つ関数です。
  2. ファンクターを間接的にアドレス指定するため、コンパイラーは関数をインライン化できない場合があります。

ファンクターをスタック上で生成すると、ファンクターのサインが表示されない可能性が高くなります。

補足: インライン ステートメントは必要ありません。クラス定義で定義されているすべての関数は、インライン化可能として扱われます。

于 2009-05-11T22:08:47.403 に答える
0

あなたの質問への答えには 2 つの側面があります。

  1. コンパイラーはヒープ割り当てを最適化しますか? 私はそれを強く疑っていますが、私は標準的な人ではないので、調べる必要があります.

  2. コンパイラは、オブジェクトの operator() をインライン化して最適化できますか? はい。呼び出しを仮想として指定しない限り、ポインターの逆参照でさえ実際には実行されません。

于 2009-05-11T10:09:34.837 に答える
0

コンパイラーはおそらく、operator() がメンバー変数を使用していないことを理解し、それを最大限に最適化できます。ただし、ローカルまたはヒープに割り当てられた変数については何も仮定しません。

編集:疑わしい場合は、コンパイラでアセンブリ出力オプションをオンにして、実際に何をしているかを確認してください。本当の答えが自分でわかるのに、ウェブでたくさんのバカの話を聞いても意味がありません。

于 2009-05-11T02:33:00.323 に答える