42

次のコードでは:

std::vector<int> var;
for (int i = 0; i < var.size(); i++);

メンバー関数はsize()ループの反復ごとに呼び出されますか、それとも 1 回だけ呼び出されますか?

4

10 に答える 10

49

理論的には、forループなので、毎回呼び出されます。

for(initialization; condition; increment)
    body;

次のようなものに拡張されます

{
    initialization;
    while(condition)
    {
        body;
        increment;
    }
}

(初期化はすでに内部スコープにあるため、中括弧に注意してください)

実際には、コンパイラーが、条件の一部がループの全期間を通じて不変であり、副作用がないことを理解している場合、それを取り除くのに十分賢い可能性があります。これはstrlen、引数が記述されていないループで(コンパイラがよく知っている)そのようなことで日常的に行われます。

ただし、この最後の条件を証明するのは必ずしも簡単ではないことに注意する必要があります。一般に、コンテナが関数に対してローカルであり、外部関数に渡されない場合は簡単です。コンテナーがローカルではなく(たとえば、参照によって渡された場合でもconst)、ループ本体に他の関数への呼び出しが含まれている場合、コンパイラーは、そのような関数がコンテナーを変更する可能性があると想定する必要があり、長さの計算をブロックします。

条件の一部を評価するのに「費用がかかる」ことがわかっている場合は、手動で最適化を行う価値があります(通常、このような条件は、ほぼ確実にインライン化されるポインター減算に要約されるため、通常はそうではありません)。


編集:他の人が言ったように、一般的にコンテナではイテレータを使用する方が良いですが、 sの場合、経由での要素へのランダムアクセスはO(1)であることが保証されているvectorため、それほど重要ではありません。operator[]実際には、ベクトルの場合、通常はポインターの合計(ベクトルベース+インデックス)と逆参照対ポインターの増分(前の要素+ 1)とイテレーターの逆参照です。ターゲットアドレスはまだ同じなので、キャッシュの局所性の観点からイテレータから何かを得ることができるとは思いません(たとえそうだとしても、タイトなループで大きな配列を歩いていない場合は、そのようなことに気付かないはずです一種の改善)。

代わりに、リストやその他のコンテナーの場合、ランダムアクセスの代わりにイテレーターを使用することが非常に重要になる可能性があります。ランダムアクセスを使用すると、リストのたびにウォークすることを意味する可能性がありますが、イテレーターをインクリメントすることは単なるポインターの逆参照です。

于 2010-10-10T18:43:36.197 に答える
6

毎回「呼び出される」のですが、実際にはインライン メソッド呼び出しである可能性が高いため、called を引用符で囲んでいます。パフォーマンスについて心配する必要はありません。

vector<int>::iterator代わりに使用しないのはなぜですか?

于 2010-10-10T18:40:14.930 に答える
5

size()メンバー関数は毎回呼び出されますが、それをインライン化しないのは本当に悪い実装であり、固定データへの単純なアクセスや 2 つのポインターの減算ではない奇妙な実装です。
とにかく、アプリケーションのプロファイリングを行い、これがボトルネックであることが判明するまでは、そのような些細なことで心配する必要はありません。

ただし、注意すべき点は次のとおりです。

  1. ベクトルのインデックスの正しい型は ですstd::vector<T>::size_type
  2. よりも遅くなるi++ 可能性のあるタイプ (たとえば、いくつかの反復子) があります++i

したがって、ループは次のようになります。

for(vector<int>::size_type i=0; i<var.size(); ++i)
  ...
于 2010-10-10T18:41:22.157 に答える
2

size() は毎回異なる値を返す可能性があるため、毎回呼び出す必要があります。

したがって、単にそうでなければならないという大きな選択肢はありません。

于 2010-10-10T18:42:04.350 に答える
1

他の人が言ったように

  • セマンティクスは、毎回呼び出されたかのようにする必要があります
  • おそらくインライン化されており、おそらく単純な関数です

その上に

  • 十分に賢いオプティマイザーは、それが副作用のないループ不変条件であると推測し、それを完全に排除できる可能性があります(これは、コードがインライン化されている場合は簡単ですが、コンパイラーがグローバル最適化を行っている場合はそうでない場合でも可能です)
于 2010-10-10T18:44:49.193 に答える
1

ただし、この方法で行うこともできます (このループが実際にベクトルのサイズを変更せずに読み取り/書き込みのみを意図している場合)。

for(vector<int>::size_type i=0, size = var.size(); i < size; ++i) 
{
//do something
}

上記のループでは、サイズがインライン化されているかどうかに関係なく、サイズへの呼び出しが 1 つだけあります。

于 2010-10-10T18:53:42.517 に答える
1

変数が「ループ本体」内で変更されていないとコンパイラが最終的に推測できる場合、私はそれを考えますvar

for(int i=0; i< var.size();i++) { 
    // loop body
}

次に、上記は次の同等のものに置き換えられます

const size_t var_size = var.size();
for( int i = 0; i < var_size; i++ ) { 
    // loop body
}

しかし、私は絶対に確信が持てないので、コメントは大歓迎です:)

また、

  • ほとんどの場合、size()メンバー関数はインライン化されているため、この問題を心配する必要はありません

  • この懸念はend()、反復子ベースのループに常に使用される 、つまりit != container.end()

  • [以下の Steve Jessop のコメントを参照] のタイプにsize_torを使用することを検討してください。vector<int>::size_typei

于 2010-10-10T19:12:34.433 に答える
0

他の人が言ったように、コンパイラは、書かれた実際のコードをどうするかを決定します。キー数値は、毎回呼び出されることです。ただし、パフォーマンスを向上させたい場合は、いくつかの考慮事項を考慮してコードを作成することをお勧めします。あなたのケースはそれらの1つですが、これら2つのコードの違いのように、他にもあります:

for (int i = 0 ; i < n ; ++i)
{
   for ( int j = 0 ; j < n ; ++j)
       printf("%d ", arr[i][j]);
   printf("\n");
}
for (int j = 0 ; j < n ; ++j)
{
   for ( int i = 0 ; i < n ; ++i)
       printf("%d ", arr[i][j]);
   printf("\n");
}

違いは、最初のものは参照ごとに RAM ページをあまり変更しないが、もう 1 つはキャッシュと TLB などを使い果たすことです。

また、インラインはそれほど役に立ちません。呼び出し関数の順序は n (ベクトルのサイズ) 回のままになるためです。ただし、いくつかの場所では役立ちますが、最善の方法はコードを書き直すことです。

しかし!コンパイラに実行させたい場合は、コードの最適化を次のように volatile にしないでください。

for(volatile int i = 0 ; i < 100; ++i)

コンパイラが最適化するのを防ぎます。パフォーマンスに関する別のヒントが必要な場合は、volatile の代わりに register を使用してください。

for(register int i = 0 ; i < 100; ++i)

コンパイラは、i を CPU レジスタから RAM に移動しないようにします。それができることは保証されていませんが、最善を尽くします;)

于 2010-10-10T19:42:23.167 に答える