16

最初の質問

効率上の理由から、ループ外のループ内でのみ使用される複雑なオブジェクト変数を宣言する必要がないようにするための、C ++の洗練されたソリューションはありますか?

詳細な説明

同僚が興味深い点を提起しました。コードポリシーでは、次のように述べています(言い換えると):変数には常に最小限のスコープを使用し、最初の初期化で変数を宣言します

コーディングガイドの例:

// [A] DO THIS
void f() {
  ...
  for (int i=0; i!=n; ++i) {
    const double x = calculate_x(i);
    set_squares(i, x*x);
  }
  ...
}

// [B] DON'T do this:
void f() {
  int i;
  int n;
  double x;
  ...
  for (i=0; i!=n; ++i) {
    x = calculate_x(i);
    set_squares(i, x*x);
  }
  ...
}

これはすべて素晴らしいことであり、プリミティブ型からオブジェクトに移行するまでは、これに問題はありません。(特定の種類のインターフェースの場合)

例:

// [C]
void fs() {
  ...
  for (int i=0; i!=n; ++i) {
    string s;
    get_text(i, s); // void get_text(int, string&);
    to_lower(s);
    set_lower_text(i, s);
  }
  ...
}

ここで、文字列sは破棄され、ループサイクルごとにメモリが解放され、その後、get_text関数はsバッファにメモリを新たに割り当てる必要があります。

次のように書く方が明らかに効率的です。

  // [D]
  string s;
  for (int i=0; i!=n; ++i) {
    get_text(i, s); // void get_text(int, string&);
    to_lower(s);
    set_lower_text(i, s);
  }

これで、sバッファに割り当てられたメモリはループの実行間で保持され、割り当てを節約できる可能性が非常に高くなります。

免責事項: 注意:これはループであり、メモリ割り当てについて話しているので、この問題を一般的に考えるのは時期尚早の最適化ではないと思います。確かに、オーバーヘッドが問題にならない場合やループがあります。ただし、開発者が最初に期待するよりもしつこい傾向があり、パフォーマンス重要なコンテキストでコードが実行される傾向があります。n

とにかく、「一般的な」ループ構造のより効率的な方法は、コードの局所性に違反し、「万が一に備えて」複雑なオブジェクトを場違いに宣言することです。これは私をかなり不安にさせます。

私はそれを次のように書くことを検討していることに注意してください:

// [E]
void fs() {
  ...
  {
    string s;
    for (int i=0; i!=n; ++i) {
      get_text(i, s); // void get_text(int, string&);
      to_lower(s);
      set_lower_text(i, s);
    }
  }
  ...
}

読みやすさがさらに損なわれるため、解決策はありません。

さらに考えてみると、関数のインターフェースはget_textとにかく非イディオムです。outparamsはとにかく昨日なので、「良い」インターフェースは値で返されます。

  // [F]
  for (int i=0; i!=n; ++i) {
    string s = get_text(i); // string get_text(int);
    to_lower(s);
    set_lower_text(i, s);
  }

ここでは、戻り値からRVOを介して構築される可能性が非常に高いため、メモリ割り当てに2倍の料金を支払うことはありません。したがって、[F]の場合、[C]と同じ割り当てオーバーヘッドを支払います。ただし、[C]の場合とは異なり、このインターフェイスバリアントを最適化することはできません。s

したがって、結論としては、最小限のスコープを使用するとパフォーマンスが低下し、クリーンなインターフェイスを使用すると、少なくとも、out-ref-paramのものが最適化の機会を妨げるよりもはるかにクリーンな値でのリターンを検討します-少なくとも一般的なケースでは。

問題は、効率を上げるためにクリーンなコードを放棄しなければならないほどではありません。問題は、開発者がそのような特殊なケースを見つけ始めるとすぐに、コーディングガイド全体([A]、[B]を参照)が権限を失うことです。 。

今の質問は次のようになります:最初の段落を参照してください

4

4 に答える 4

2

[例 D の開始 ...] と書く方が明らかに効率的です。

私はこれを少し疑っています。ループの外から始めるデフォルトの構築にお金を払っています。ループ内で、get_textバッファーの再割り当てを呼び出す可能性があります ( yourget_textと thestringがどのように定義されているかによって異なります)。これにより、一部の実行では実際に改善が見られる場合があり (たとえば、文字列が徐々に短くなる場合)、一部の実行では (反復ごとに文字列の長さが約 2 倍になる場合)、パフォーマンスが大幅に向上することに注意してください。

不変条件がボトルネックになる場合は、不変条件をループから引き上げることは完全に理にかなっています (プロファイラーが教えてくれます)。それ以外の場合は、慣用的なコードを使用してください。

于 2012-05-26T20:11:27.067 に答える
1

の実装に依存しget_textます。

ほとんどの場合、文字列オブジェクトに割り当てられたスペースを再利用するように実装できる場合は、ループの反復ごとに新しい動的メモリ割り当てを回避するために、ループの外側でオブジェクトを確実に宣言してください

動的割り当てはコストがかかり(最適なシングルスレッドアロケータは単一の割り当てに約40の命令を必要とし、マルチスレッドはオーバーヘッドを追加し、すべてのアロケータが「最適」であるとは限りません)、メモリを断片化する可能性があります。

(BTWはstd::string通常、いわゆる「小さな文字列の最適化」を実装します。これにより、小さな文字列の動的な割り当てが回避されます。したがって、ほとんどの文字列が十分に小さく、実装がstd::string変更されないことがわかっている場合は、理論的には動的な割り当てを回避できます。各反復で新しいオブジェクトを作成するとき。ただし、これは非常に壊れやすいので、お勧めしません。)


一般的に、それはすべて、それらを使用するオブジェクトと関数がどのように実装されているかに依存します。パフォーマンスを重視する場合は、このような「抽象化リーク」にケースバイケースで対処する必要があります。したがって、戦闘を賢く選択してください。最初にボトルネックを測定して最適化します。

于 2012-05-26T20:48:32.327 に答える
1

私は次のいずれかを行います:

  • これらの重量級のルールに例外を設けてください。「D」のように、必要に応じてスコープを制限できることに注意してください。
  • ヘルパー関数を許可します (文字列はパラメーターにすることもできます)
  • それらが本当に気に入らない場合はfor、カウンター/イテレーターと一時オブジェクトを保持する複数要素オブジェクトを使用して、ループのスコープでローカルを宣言できます。std::pair<int,std::string>特殊なコンテナーを使用すると構文上のノイズを減らすことができますが、1 つのオプションになります。

(そして、多くの場合、out パラメータは RVO スタイルよりも高速です)

于 2012-05-26T20:09:50.733 に答える
0

文字列クラスのコピー オン ライト実装がある場合、to_lower(s) はいずれにせよメモリを割り当てるため、ループの外で s を宣言するだけでパフォーマンスが向上するかどうかは明確ではありません。

私の意見では、2 つの可能性があります。次に、宣言をループの外に置くのは論理的に簡単です。2.) コンストラクターが何の役にも立たないクラスがあり、その宣言をループ内に入れます。

1. が true の場合、おそらくオブジェクトをヘルパー オブジェクトに分割する必要があります。たとえば、領域を割り当てて重要な初期化を行うヘルパー オブジェクトと、flyweight オブジェクトです。次のようなもの:

StringReservedMemory m (500); /* base object for something complex, allocating 500 bytes of space */
for (...) {
   MyOptimizedStringImplementation s (m);
   ...
}
于 2012-05-26T20:46:55.857 に答える