더 나은 성능을 제공하는 코드 스 니펫은 무엇입니까? 아래 코드 세그먼트는 C #으로 작성되었습니다.
1.
for(int counter=0; counter<list.Count; counter++)
{
list[counter].DoSomething();
}
2.
foreach(MyType current in list)
{
current.DoSomething();
}
답변
음, 부분적으로 정확한 유형에 따라 다릅니다 list
. 또한 사용중인 정확한 CLR에 따라 다릅니다.
어떤 식 으로든 중요한지 여부는 루프에서 실제 작업을 수행하는지 여부에 따라 다릅니다. 거의 모두 경우에 성능 차이는 크지 않지만 가독성의 차이는 foreach
루프를 선호합니다 .
개인적으로 LINQ를 사용하여 “if”도 피할 것입니다.
foreach (var item in list.Where(condition))
{
}
편집 : List<T>
with 를 반복 foreach
하면 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
컴파일러는 배열을 다르게 처리 하여 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는> 클래스도, 인덱서가 발생하지 않는 것을 발견 할 것이다; 하지만 가능하다면 인덱서는 다음과 같이 작성해야합니다.
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)
{
}
}
나는 foreach 문으로의 대체 경우 – foreach는 20 밀리 초 빨랐다 – 일관 . foreach는 135-139ms이고 foreach는 113-119ms입니다. 나는 여러 번 앞뒤로 바꿨고 방금 시작된 프로세스가 아닌지 확인했습니다.
그러나 foo 및 if 문을 제거하면 for가 30ms 빨라졌습니다 (foreach는 88ms이고 for는 59ms였습니다). 둘 다 빈 껍질이었습니다. foreach가 실제로 변수를 증가시키는 변수를 전달했다고 가정합니다. 내가 추가하면
int foo = intList[i];
그러면 for는 약 30ms 느려집니다. 나는 이것이 foo를 만들고 배열에서 변수를 잡고 foo에 할당하는 것과 관련이 있다고 가정하고 있습니다. intList [i]에 액세스하면 해당 패널티가 없습니다.
솔직히 말해서 .. 나는 foreach가 모든 상황에서 약간 느려질 것으로 예상했지만 대부분의 응용 프로그램에서 중요하지는 않습니다.
편집 : 여기 Jons 제안을 사용하는 새 코드가 있습니다 (134217728은 System.OutOfMemory 예외가 발생하기 전에 가질 수있는 가장 큰 정수입니다) :
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 #에 인덱서가 없기 때문에 C #보다 Java에 더 많이 적용 LinkedLists
되지만 일반적인 요점은 여전히 유지하다고 생각합니다.
경우 list
작업중인이를 될 일이 LinkedList
, 인덱서 코드 (의 성능은 배열 스타일 에 접근) 훨씬 더 나쁜 사용하는 것보다 IEnumerator
로부터를 foreach
큰 목록에 대해.
LinkedList
인덱서 구문 :을 사용하여 a 요소 10.000에 액세스 list[10000]
하면 연결된 목록이 헤드 노드에서 시작 Next
하여 올바른 개체에 도달 할 때까지 -pointer를 10,000 번 순회 합니다. 분명히 루프에서이 작업을 수행하면 다음을 얻을 수 있습니다.
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
다만 일정한 시간에 작동 하는 반면 .
물론 Jon이 말했듯이 이것은 실제로 유형에 따라 달라집니다 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 및 기타 “기능”의 지지자이기도합니다.
성능에 미치는 영향에 대해서는 이 질문에 대한 답변을 참조하십시오 .
편집 : 인덱서가없는 C # (HashSet와 같은) 컬렉션이 있습니다. 이 컬렉션에서 foreach 는 반복하는 유일한 방법 이며 .NET 용 으로 사용해야한다고 생각하는 유일한 경우 입니다 .
답변
두 루프의 속도를 테스트 할 때 쉽게 놓칠 수있는 흥미로운 사실이 있습니다. 디버그 모드를 사용하면 컴파일러가 기본 설정을 사용하여 코드를 최적화 할 수 없습니다.
이로 인해 foreach가 디버그 모드보다 빠르다는 흥미로운 결과가 나왔습니다. for는 릴리스 모드에서 foreach보다 빠르지 않습니다. 분명히 컴파일러는 여러 메서드 호출을 손상시키는 foreach 루프보다 for 루프를 최적화하는 더 좋은 방법을 가지고 있습니다. 그런데 for 루프는 기본적으로 CPU 자체에 의해 최적화 될 수도 있습니다.