web-dev-qa-db-ja.com

foreachループを実行する前にアイテム数を確認することで、パフォーマンス上の利点はありますか?

私はこれをコードで見て、ループする前にアイテム数をチェックすることでパフォーマンス上の利点があるかどうか疑問に思いました:

if (SqlParams.Count > 0)
    foreach (var prm in SqlParams)
        cmd.Parameters.Add(prm);

私は常に代わりにnullチェックを実行し、foreachループに0項目がある場合はループアウトすることを常に好んでいます。

if (SqlParams != null)
    foreach (var prm in SqlParams)
        cmd.Parameters.Add(prm);

それはより良い方法ではありませんか?

6
Code Maverick

最終的に、最良の答えは実際にテストすることです。最初に長さをチェックして、またはチェックせずに空の配列をループするメソッドを作成し、それぞれ100,000回呼び出して、実行時間が速いメソッドを確認します。

_public void withCheck(Integer[] array) {
    for (int i = 0; i < 100000; i++) {
        if (array.length > 0) {
            for (Integer i : array) {
                // nothing to do.
            }
        }
    }
}

public void withoutCheck(Integer[] array) {
    for (int i = 0; i < 100000; i++) {
        for (Integer i : array) {
            // nothing to do.
        }
    }
}

public void test() {
    long startTime = System.currentTimeMillis();
    withCheck();
    System.out.println("Time with check: " + (System.currentTimeMillis() - startTime) + "ms");
    startTime = System.currentTimeMillis();
    withoutCheck();
    System.out.println("Time without check: " + (System.currentTimeMillis() - startTime) + "ms");
}
_

このようなコードを書くとき、私は通常、これをコードの可読性と認識されるパフォーマンスまたは潜在的なパフォーマンスとの間のトレードオフとして考えます。言い換えると、たとえif (count > 0)チェックを追加すると、たとえ非常に少量であっても、読みやすさに悪影響を及ぼします。それで、利益は何ですか?利点はほとんどないかまったくないと思いますが、あなたのように、実際にはテストしていません。私が言えることは、私のすべての年のプロファイリングで、空の配列をループすることがボトルネックになることは一度もないということです。

技術的には、イテレータがどのように機能するかによって異なります。あなたが持っているようなforeachループの場合は、舞台裏で反復子オブジェクトを作成しています。私の期待は、これも検証されていませんが、イテレータオブジェクトFOR AN ARRAYは次のように実装されるだけです(これはJavaですが、C#はおそらく同等です)。

_public class ArrayIterator<T> implements Iterator<T> {
    private int next = 0;
    public T next() {
        return backingArray[next++];
    }

    public boolean hasNext() {
        return next < backingArray.length;
    }
}
_

そのため、hasNext()はfalseを返すため、このようなイテレータの反復処理は非常に迅速に終了する必要があります。 ifステートメントが行うのと同じチェックであることに注意してください。

リンクされたリストを反復処理する場合、実装は異なるはずです。サイズを明示的に確認すると、リンクされたリストを強制的にスキャンする可能性があります。 Javaでは、LinkedList.size()が明示的なsize変数をチェックしてリストをたどらないようにしますが、.NETがそれを実装する方法がわかりません。

これを見る別の方法は、コレクションが空である可能性がどのくらいあるかです。それがまれなケースである場合、空の配列を反復するのに時間がかかるという理論が真である場合、最初に空の明示的なチェックを追加すると、コレクションが通常は空である場合にのみ高速になります。そうでない場合、理論的には、最悪のケースをわずかに加速するために、平均的なケースを減速しています。

繰り返しますが、これらはすべての理論であり、タイミングはマイクロレベルであるので、一粒の塩でそれを取ってください。

TL; DR

コレクションの種類、ランタイムプラットフォーム、一般的なコレクションサイズによって異なりますが、ほとんどの場合、直感的には速度を上げることは難しいようです。しかし、確実に知る唯一の方法は、それをテストすることです。

7
Brandon

本当にそうではありません。なぜなら、0項目がある場合、foreachループのセットアップはそれをとにかく見つけて、ループの本体を実行しないだけだからです。

理論的には非常にタイトなループで、コレクションが空であることが一般的であり、カウントを見つけることは非常に安価な操作であり、ここでは顕著なパフォーマンス上の利点がありますが、データベースクエリを実行している場合、それはほぼ間違いなくそうではありません。したがって、プロファイリングで実際にかなりの実行時間がかかっていることが示されない限り、心配する必要はありません。

9
Mason Wheeler

_System.Collections.Generic.List<T>_の ソースコード を見ると、MoveNext()が次のように実装されていることがわかります。

_public bool MoveNext() {
    List<T> localList = list;

    if (version == localList._version && ((uint)index < (uint)localList._size)) 
    {                                                     
        current = localList._items[index];                    
        index++;
        return true;
    }
    return MoveNextRare();
}
_

1つのチェックの合計コストは

  • 既存のコレクションを別の変数に割り当てる
  • バージョンの比較、それが何であれ
  • 2つのint値をuintにキャストして比較する

_.Count_の ソースコード を見ると、

_public int Count {
    get {
        lock (_root) { 
            return _list.Count; 
        }
    }
}
_

コストがロックを獲得していることがわかります。

結論

測定してどれが最も効果的かを確認できますが、これは問題にならないと結論付けるのは安全だと思います。初期のサイズチェックを行わない、繰り返し処理する珍しいコレクションがあるかもしれませんが、それはそれ自体を指摘する非常に特殊なEdgeのケースです。

結論:心配する必要はありません。最初に_.Count > 0_または__collection != null_チェックを追加する必要はありません。

4
Jeroen Vannevel

Countを最初にチェックするとパフォーマンスが向上する唯一のシナリオは、リソースを大量に消費するIEnumerable.MoveNext()実装と、非常に効率的な_ICollection.Count_実装を含むコレクションを使用する場合です。技術的には可能ですが、発生する可能性はほとんどありません

実際、いくつかのシナリオでは、最初にCount()をチェックすると、実際にはlessパフォーマンスが向上します。 linqIQueryableインターフェイスを利用するEntity FrameworkのようなORMフレームワークの例を考えてみてください。これらのオブジェクトは背後でデータストアに到達し、オブジェクトのコレクションと、合計、平均、およびもちろんカウントなどの演算処理結果を提供します。ここでIQueryable.Count()を呼び出すと、実際にデータベースでSELECT COUNT(*) ...クエリが実行されます。あなたがその情報を必要としないなら、それを得るのは単に不必要なオーバーヘッドでしょう。

結論:実際にコーディングスタイルを変更することはありません。コレクションが空であることが判明した場合でも、コレクションをすぐにループすることは完全に許容されます。出所がわからない場合は、最初にnullでないことを確認しても問題ありません。

3
Crono

短い答えは:いいえ。

詳細: SqlParameterCollection はIEnumerableインターフェースを実装しており、foreachループを使用できます。foreach自体は、例外が発生しないことを保証しますが、 System.NullReferenceException Null例外は(Count)プロパティとは何の関係もないことを考慮して、コレクションに0のパラメーターを含めることができ、変数が初期化されます(nullではありません)。

したがって、何をする必要があるかは、foreach構文の前にnullを確認するだけです。

1
Sari

システムのパフォーマンスと単一の実行を比較します。リストが99%の時間で空でない場合は、ほとんどの場合、追加のチェックのコストを支払っています。

一方、リストが99%の時間空である場合は、別の方法で機能する可能性があります(ただし、保存するたびに追加の呼び出しが相殺されるのではないかと思います-マイクロ秒またはナノ秒についてはこの例)。

したがって、1つのスレッドで1つのパスが実行されていると考えるだけでなく、システムのパフォーマンスをすべて考慮する必要があります。

1
Rob

利点は柔軟性の形で提供されますが、パフォーマンス上の利点はありません。

foreachICollectionを0のカウントで実行する場合、列挙子はセット全体をすでにトラバースしたものとして登録されます。機能的には、次のforループに似ています。

for(int i = 0; i > -1; i++)
    foo();

さて、foreachの前に実行する必要がある他のビジネスルールがある場合、それはまったく別の話であり、if条件があると、その将来の機能にうまく対応できます。ただし、厳密にも必要ありません。

編集:あなたの編集に応じて、コレクションはnullになる可能性があるため、nullチェックはvastlyより良い方法です。

IEnumerablesのIsNullOrEmpty拡張メソッドを作成するのは、次のような繰り返しの状態が何度も見られるためです。

if(SomeCollection != null
   && SomeCollection.Count > 0
   && (SomeOtherCondition))
{
    // Stuff that relies on SomeCollection having at least one value.
}
1
Andrew Gray