パフォーマンスを向上させるコードスニペットはどれですか?以下のコードセグメントはC#で記述されています。
1。
for(int counter=0; counter<list.Count; counter++)
{
list[counter].DoSomething();
}
2。
foreach(MyType current in list)
{
current.DoSomething();
}
まあ、それは部分的にlist
の正確なタイプに依存します。また、使用している正確なCLRにも依存します。
何らかの方法でsignificantであるかどうかは、ループで実際の作業を行っているかどうかによって決まります。ほぼallの場合、パフォーマンスの違いはそれほど大きくありませんが、読みやすさの違いはforeach
ループを優先します。
私も個人的にLINQを使用して「if」を回避します。
foreach (var item in list.Where(condition))
{
}
編集:foreach
でList<T>
を反復処理するとfor
ループと同じコードが生成されると主張している人のために、そうではないという証拠があります:
static void IterateOverList(List<object> list)
{
foreach (object o in list)
{
Console.WriteLine(o);
}
}
次のILを生成します。
.method private hidebysig static void IterateOverList(class [mscorlib]System.Collections.Generic.List`1<object> list) cil managed
{
// Code size 49 (0x31)
.maxstack 1
.locals init (object V_0,
valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object> V_1)
IL_0000: ldarg.0
IL_0001: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<object>::GetEnumerator()
IL_0006: stloc.1
.try
{
IL_0007: br.s IL_0017
IL_0009: ldloca.s V_1
IL_000b: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::get_Current()
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: call void [mscorlib]System.Console::WriteLine(object)
IL_0017: ldloca.s V_1
IL_0019: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::MoveNext()
IL_001e: brtrue.s IL_0009
IL_0020: leave.s IL_0030
} // end .try
finally
{
IL_0022: ldloca.s V_1
IL_0024: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>
IL_002a: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_002f: endfinally
} // end handler
IL_0030: ret
} // end of method Test::IterateOverList
コンパイラはarraysを異なる方法で処理し、foreach
ループを基本的にfor
ループに変換しますが、List<T>
は変換しません。配列の同等のコードは次のとおりです。
static void IterateOverArray(object[] array)
{
foreach (object o in array)
{
Console.WriteLine(o);
}
}
// Compiles into...
.method private hidebysig static void IterateOverArray(object[] 'array') cil managed
{
// Code size 27 (0x1b)
.maxstack 2
.locals init (object V_0,
object[] V_1,
int32 V_2)
IL_0000: ldarg.0
IL_0001: stloc.1
IL_0002: ldc.i4.0
IL_0003: stloc.2
IL_0004: br.s IL_0014
IL_0006: ldloc.1
IL_0007: ldloc.2
IL_0008: ldelem.ref
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: call void [mscorlib]System.Console::WriteLine(object)
IL_0010: ldloc.2
IL_0011: ldc.i4.1
IL_0012: add
IL_0013: stloc.2
IL_0014: ldloc.2
IL_0015: ldloc.1
IL_0016: ldlen
IL_0017: conv.i4
IL_0018: blt.s IL_0006
IL_001a: ret
} // end of method Test::IterateOverArray
興味深いことに、C#3仕様にこのドキュメントが記載されていません...
for
ループは、これとほぼ同等のコードにコンパイルされます。
int tempCount = 0;
while (tempCount < list.Count)
{
if (list[tempCount].value == value)
{
// Do something
}
tempCount++;
}
foreach
ループが、これとほぼ同等のコードにコンパイルされる場所:
using (IEnumerator<T> e = list.GetEnumerator())
{
while (e.MoveNext())
{
T o = (MyClass)e.Current;
if (row.value == value)
{
// Do something
}
}
}
ご覧のとおり、列挙子の実装方法とリストインデクサーの実装方法にすべて依存します。結局のところ、配列に基づく型の列挙子は通常、次のように記述されます。
private static IEnumerable<T> MyEnum(List<T> list)
{
for (int i = 0; i < list.Count; i++)
{
yield return list[i];
}
}
ご覧のとおり、この例ではそれほど違いはありませんが、リンクリストの列挙子はおそらく次のようになります。
private static IEnumerable<T> MyEnum(LinkedList<T> list)
{
LinkedListNode<T> current = list.First;
do
{
yield return current.Value;
current = current.Next;
}
while (current != null);
}
。NET では、LinkedList <T>クラスにはインデクサーさえないため、リンクリストでforループを実行することはできません。ただし、可能であれば、インデクサーは次のように記述する必要があります。
public T this[int index]
{
LinkedListNode<T> current = this.First;
for (int i = 1; i <= index; i++)
{
current = current.Next;
}
return current.value;
}
ご覧のとおり、ループ内でこれを複数回呼び出すと、リスト内の場所を記憶できる列挙子を使用するよりもはるかに遅くなります。
半検証する簡単なテスト。ちょっとしたテストをしてみました。コードは次のとおりです。
static void Main(string[] args)
{
List<int> intList = new List<int>();
for (int i = 0; i < 10000000; i++)
{
intList.Add(i);
}
DateTime timeStarted = DateTime.Now;
for (int i = 0; i < intList.Count; i++)
{
int foo = intList[i] * 2;
if (foo % 2 == 0)
{
}
}
TimeSpan finished = DateTime.Now - timeStarted;
Console.WriteLine(finished.TotalMilliseconds.ToString());
Console.Read();
}
そして、これがforeachセクションです。
foreach (int i in intList)
{
int foo = i * 2;
if (foo % 2 == 0)
{
}
}
Forをforeachに置き換えると、foreachは20ミリ秒速くなりました-consistently。 forは135-139msで、foreachは113-119msでした。私は何度かやり取りしましたが、それがちょうど始まったプロセスではないことを確認しました。
ただし、fooとifステートメントを削除すると、forは30ミリ秒速くなりました(foreachは88ミリ秒、forは59ミリ秒)。どちらも空のシェルでした。 foreachが実際に変数を渡したのは、forが変数をインクリメントしているだけだと想定しているからです。追加した場合
int foo = intList[i];
その後、forは約30ms遅くなります。これは、fooを作成し、配列内の変数を取得してfooに割り当てることに関係していると思います。 intList [i]にアクセスするだけであれば、そのペナルティはありません。
正直なところ、私はforeachがすべての状況でわずかに遅くなると思っていましたが、ほとんどのアプリケーションでは問題にはなりません。
編集:これはJonsの提案を使用した新しいコードです(134217728は、System.OutOfMemory例外がスローされる前に持つことができる最大のintです):
static void Main(string[] args)
{
List<int> intList = new List<int>();
Console.WriteLine("Generating data.");
for (int i = 0; i < 134217728 ; i++)
{
intList.Add(i);
}
Console.Write("Calculating for loop:\t\t");
Stopwatch time = new Stopwatch();
time.Start();
for (int i = 0; i < intList.Count; i++)
{
int foo = intList[i] * 2;
if (foo % 2 == 0)
{
}
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
Console.Write("Calculating foreach loop:\t");
time.Reset();
time.Start();
foreach (int i in intList)
{
int foo = i * 2;
if (foo % 2 == 0)
{
}
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
Console.Read();
}
結果は次のとおりです。
データを生成します。 forループの計算:2458ms foreachループの計算:2005ms
それらを入れ替えて、物事の順序を処理するかどうかを確認すると、ほぼ同じ結果が得られます。
注:この答えは、C#がLinkedLists
にインデクサーを持たないため、C#に比べてJavaに適用されますが、一般的なポイントはまだを保持します。
使用しているlist
がLinkedList
である場合、インデクサーコード(array-style access)のパフォーマンスは、使用するよりもはるかに悪いです。大きなリストの場合は、IEnumerator
からのforeach
。
インデクサー構文を使用して、LinkedList
内の要素10.000にアクセスする場合:list[10000]
、リンクリストはヘッドノードから開始し、Next
- pointerを1万回走査して、正しいオブジェクトに到達します。明らかに、これをループで実行すると、次の結果が得られます。
list[0]; // head
list[1]; // head.Next
list[2]; // head.Next.Next
// etc.
GetEnumerator
を(暗黙的にforach
- syntaxを使用して)呼び出すと、ヘッドノードへのポインターを持つIEnumerator
オブジェクトを取得します。 MoveNext
を呼び出すたびに、そのポインターは次のように次のノードに移動します。
IEnumerator em = list.GetEnumerator(); // Current points at head
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
// etc.
ご覧のとおり、LinkedList
sの場合、ループが長くなるほど、配列インデクサーメソッドはますます遅くなります(同じヘッドポインターを何度も繰り返す必要があります)。 IEnumerable
は一定時間で動作します。
もちろん、ジョンが言ったように、これはlist
のタイプに本当に依存します。list
がLinkedList
ではなく配列である場合、動作は完全に異なります。
他の人が言及したように、パフォーマンスは実際にはそれほど重要ではありませんが、foreachは、ループでのIEnumerable
/IEnumerator
の使用のため、常に少し遅くなります。コンパイラーはその構成をそのインターフェース上の呼び出しに変換し、すべてのステップで、関数+プロパティがforeach構成で呼び出されます。
IEnumerator iterator = ((IEnumerable)list).GetEnumerator();
while (iterator.MoveNext()) {
var item = iterator.Current;
// do stuff
}
これは、C#の構造の同等の展開です。 MoveNextおよびCurrentの実装に基づいて、パフォーマンスへの影響がどのように変化するか想像できます。一方、配列アクセスでは、その依存関係はありません。
「foreachループは読みやすさのために優先されるべきです」という十分な引数を読んだ後、私の最初の反応は「何」だったと言えますか?一般的に、読みやすさは主観的であり、この特定の例ではさらに読みやすくなります。プログラミング(実際にはJavaより前のすべての言語)のバックグラウンドを持つ人にとって、forループはforeachループよりも読みやすいです。さらに、foreachループの方が読みやすいと主張している同じ人々は、コードの読み取りと保守を困難にするlinqおよびその他の「機能」の支持者でもあり、これは上記のポイントを証明しています。
パフォーマンスへの影響については、 this 質問への回答を参照してください。
編集:C#には、インデクサーを持たないコレクション(HashSetなど)があります。これらのコレクションでは、foreachが反復する唯一の方法であり、それが使用されるべきだと思う唯一のケースですfor.
提供した例では、foreach
ループの代わりにfor
ループを使用することをお勧めします。
標準のforeach
コンストラクトは、ループが展開されていない限り(ステップあたり1.0サイクル)、単純なfor-loop
(ステップあたり2サイクル)よりも高速(ステップあたり1.5サイクル)になります。
したがって、日常のコードでは、パフォーマンスは、より複雑なfor
、while
、またはdo-while
構造を使用する理由ではありません。
このリンクを確認してください: http://www.codeproject.com/Articles/146797/Fast-and-Less-Fast-Loops-in-C
╔══════════════════════╦═══════════╦═══════╦════════════════════════╦═════════════════════╗
║ Method ║ List<int> ║ int[] ║ Ilist<int> onList<Int> ║ Ilist<int> on int[] ║
╠══════════════════════╬═══════════╬═══════╬════════════════════════╬═════════════════════╣
║ Time (ms) ║ 23,80 ║ 17,56 ║ 92,33 ║ 86,90 ║
║ Transfer rate (GB/s) ║ 2,82 ║ 3,82 ║ 0,73 ║ 0,77 ║
║ % Max ║ 25,2% ║ 34,1% ║ 6,5% ║ 6,9% ║
║ Cycles / read ║ 3,97 ║ 2,93 ║ 15,41 ║ 14,50 ║
║ Reads / iteration ║ 16 ║ 16 ║ 16 ║ 16 ║
║ Cycles / iteration ║ 63,5 ║ 46,9 ║ 246,5 ║ 232,0 ║
╚══════════════════════╩═══════════╩═══════╩════════════════════════╩═════════════════════╝
Deep .NET-part 1 Iteration でそれについて読むことができます
.NETソースコードから逆アセンブリまでの結果(最初の初期化なし)をカバーします。
両方のループの速度をテストするときに簡単に見落とされる可能性がある、さらに興味深い事実があります。デバッグモードを使用しても、コンパイラはデフォルト設定を使用してコードを最適化しません。
これにより、foreachがデバッグモードの場合よりも高速であるという興味深い結果に至りました。一方、forはリリースモードのforeachより高速です。明らかに、コンパイラは、いくつかのメソッド呼び出しを危険にさらすforeachループよりも、forループを最適化するためのより良い方法を持っています。 forループは、これがCPU自体によって最適化される可能性があるほど基本的な方法です。