-1

関数の作成に関しては、常に 2 つの陣営があるようです。

  1. 関数内で 1 つの return が見つかるようにしたいもの (通常は最後に)
  2. データの直線的な流れが好きな人 (通常、複数のリターンがある)

通常、これはより個人的なスタイルですが、パフォーマンスについてお話したいと思います。質問する前に、まず仮説的なシナリオを提案させてください。

要件の状態を考えてみましょう:

Function should first try to calc ReturnVal based off of A. (Typical Case)
If A is unable to be determined then try to find based off of B. 
If B is unable to be determined then try to find based off of C. 
If all else fails then return C since that is always known.
Function should return enum which states which way the value was found.

したがって、次のようなものがあるとしましょう。

enum HowFound
{
  eWithA,
  eWithB,
  eWithC
};

HowFound CalcReturn(int& nValue) const;

私の質問 速度 (jmp の数など) に関して、ほとんどの場合、コンパイラーがより適切に最適化できると思うスタイルはどれですか?

スタイル 1

HowFound CalcReturn(int& nValue) const
{
  HowFound howFound = eWithA;

  const int valWithB = CalcWithB();
  const int valWithC = CalcWithC();

  nValue = CalcWithA( valWithB, valWithC );
  if (nValue == -1)
  {
    nValue = valWithB;
    howFound = eWithB;
    if (nValue == -1)
    {
       nValue = valWithC;
       howFound = eWithC;
    }
  }

  return howFound; 
}

スタイル 1 では、戻り値を格納し、関数を早期に終了することはありません。1 つのリターンがあり、それは最後にあります。

スタイル 2

HowFound CalcReturn(int& nValue) const
{
  const int valWithB = CalcWithB();
  const int valWithC = CalcWithC();

  nValue = CalcWithA( valWithB, valWithC );
  if (nValue != -1)
  {
    return eWithA;
  }

  if (valWithB != -1)
  {
    nValue = valWithB;
    return eWithB;
  }

  nValue = valWithC;
  return eWithC;
}

スタイル 2 では、データ フローはより「直線的」になります。値を見つけるとすぐに、関数を終了します。これは、関数内により多くの終了ポイントがあることを意味します。

免責事項:明らかに、各スタイルを微調整できますが、私の質問は同じままです. また、はい、両方の関数を記述して逆アセンブリをチェックすることもできますが (これは私が持っています)、「詳細」が追加されると結果が変わります。私の質問は、どちらのスタイルがパフォーマンスが向上する可能性が高いかについてです (どちらかの場合)。

ありがとう!

4

4 に答える 4

1

コンパイラによって行われる最適化は大きく異なりますが、どちらのコードもほぼ同等に効率的なコードに縮小されます。したがって、実行時間はほぼ同じになります。ただし、クロック サイクルの節約を探している場合は、テストする必要があります。その場合、話はここで終わりではありません。また、ハードウェアの最適化、特に分岐予測子にも依存します。したがって、値の頻度とパスがわかっている場合は、パスに合わせてコードを再構築することで、クロック サイクル レベルで節約することができます。これを探していない場合は、読みやすさを重視してください。

于 2013-02-01T16:08:58.783 に答える
0

コンパイラは、両方の例を同じコードに変換します。「どちらが読みやすいか」が重要です [これは、コードがアルゴリズムの実際の説明とどのように一致するかによって異なります]。

編集:「まともな最適化コンパイラ」と言うべきです-非常に初歩的で最適化が不十分なコンパイラは、実際には何も再配置せずにあなたが求めていることを正確に実行する可能性があり、したがって、あるケースでは他のケースよりも優れている/劣っている. しかし、不必要に長い操作を行うようなコードを意図的に書かない限り (たとえば、長い文字列に対して strlen() を呼び出したり、 is_prime(101218819) などを呼び出したりして、値を使用しない場合でも、同じ結果が得られるはずです)。結果)。いつものように、[プロファイリングに基づいて] これがコードの重要な部分であることがわかっている場合は、プロジェクトに使用するコンパイラと設定を試してみて、違いがあるかどうかを確認してください。インターネットは、コードのクリティカル セクションのベンチマークに代わるものではありません。

于 2013-02-01T15:59:29.507 に答える
0

ほとんどの場合、コンパイラは両方のスタイルに対して同一のコードを生成しますが、特定の実装は効率が悪い可能性があることに注意してください (最も公平な比較はelses で割り当てられます。あなたのものは割り当ててから再割り当てします)。

ただし、もちろん、コンパイラは異なる場合があります。

于 2013-02-01T15:57:54.157 に答える
-2

https://stackoverflow.com/faq#dontask

この質問は、次の理由から不適切である可能性があります。

  • 解決すべき実際の問題はありません。「他の人が私と同じように感じているかどうか知りたいです。」</li>
  • これは、質問を装った暴言です: 「______ 最悪ですよね?」</li>

それはさておき、OPもこの問題についてあまり考えていません。

これは 3 番目のスタイルですか。

 HowFound CalcReturn(int& nValue) const
 {
   const int valWithB = CalcWithB();
   const int valWithC = CalcWithC();
   HowFound howFound = eWithC;
   nValue = CalcWithA( valWithB, valWithC );
   if (nValue != -1)
   {
     howFound= eWithA;
   }
   else if (valWithB != -1)
   {
     nValue = valWithB;
     howFound = eWithB;
   } 
   else {
     nValue = valWithC;
   }
   return howFound;
 }

このスタイルはAですか、それともBですか?linearのどのような直感によっても線形に見えます。1 つの戻り値があり、3 つの関数すべてが同じコードにコンパイルされる可能性があります。一部の最新のコンパイラでは、NRVOは単一の終了ポイント ( http://msdn.microsoft.com/en-us/library/ms364057%28v=vs.80%29.aspx#nrvo_cpp05_topic3、例 4) でより適切に機能します。

複雑さを増加または減少させましたか?

これは速いですか遅いですか?

このコードを変換するには、他にいくつの方法がありますか?

どちらがメンテナンスしやすいかを判断するにはどうすればよいですか?

戻り値が 1 つだけの関数を別の関数に変換するたびに、必ずコードが同等またはより複雑になると言えますか?

とにかく、より複雑な意味は何ですか?

これらのいくつかは、自由回答式の質問です...

于 2013-02-01T16:34:20.353 に答える