Jiang Min 형제는 얼마 전 Array.Sort 및 Enumerable.OrderBy 메소드에 대해 충분히 엄격한 성능 테스트를 수행했다고 합니다. 이론과 기대에 어긋나는 결론인 것 같지만, 비교적 엄격한 환경에서 측정된 결론이라 전문가들의 관심도 쏠렸다. 물론 내 컴퓨터에서도 테스트했으며 관련 없는 코드를 먼저 분류했습니다.
시스템 사용;
System.Collections.Generic을 사용합니다.
System.Diagnostics 사용;
System.Linq 사용;
System.Runtime.InteropServices 사용;
System.Threading 사용;
네임스페이스 Exam11
{
공개 정적 클래스 CodeTimer
{
공개 정적 무효 초기화()
{
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
시간( "", 1, () => { } );
}
public static void Time(문자열 이름, 정수 반복, 작업 작업)
{
if ( String.IsNullOrEmpty( 이름 ) ) 반환;
// 워밍업
행동();
// 1.
ConsoleColor currentForeColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(이름);
// 2.
GC.Collect( GC.MaxGeneration, GCCollectionMode.Forced );
int[] gcCounts = new int[GC.MaxGeneration + 1];
for ( int i = 0; i <= GC.MaxGeneration; i++ )
{
gcCounts[i] = GC.CollectionCount(i);
}
// 3.
스톱워치 시계 = new Stopwatch();
시계.시작();
ulong CycleCount = GetCycleCount();
for ( int i = 0; i < 반복; i++ ) action();
ulong cpuCycles = GetCycleCount() - 사이클카운트;
watch.Stop();
// 4.
Console.ForegroundColor = currentForeColor;
Console.WriteLine( "t경과 시간:t" + watch.ElapsedMilliseconds.ToString( "N0" ) + "ms" );
Console.WriteLine( "tCPU 사이클:t" + cpuCycles.ToString( "N0" ) );
// 5.
for ( int i = 0; i <= GC.MaxGeneration; i++ )
{
int count = GC.CollectionCount(i) - gcCounts[i];
Console.WriteLine( "tGen " + i + ": tt" + count );
}
Console.WriteLine();
}
개인 정적 ulong GetCycleCount()
{
긴 사이클카운트 = 0;
QueryThreadCycleTime( GetCurrentThread(), ref CycleCount );
사이클카운트 반환;
}
[DllImport( "kernel32.dll" )]
[반환: MarshalAs(UnmanagedType.Bool)]
static extern bool QueryThreadCycleTime(IntPtr threadHandle, ref ulong CycleTime);
[DllImport( "kernel32.dll" )]
정적 외부 IntPtr GetCurrentThread();
}
수업 프로그램
{
정적 무효 Main( string[] args )
{
var random = new Random(DateTime.Now.Millisecond);
var array = Enumerable.Repeat( 0, 50000 ).Select( _ => new Person { ID = 무작위.Next() } ).ToArray();
//라오자오 프로그램
CodeTimer.Initialize();
CodeTimer.Time( "SortWithCustomComparer", 500, () => SortWithCustomComparer( CloneArray( array ) ) );
CodeTimer.Time( "SortWithLinq", 500, () => SortWithLinq( CloneArray( 배열 ) ) );
Console.ReadLine();
}
개인 정적 무효 SortWithCustomComparer(Person[] 배열)
{
Array.Sort( array, new PersonComparer() );
}
개인 정적 무효 SortWithLinq(Person[] 배열)
{
var 정렬 =
(배열에 있는 사람으로부터
주문자별.ID
사람 선택 ).ToList();
}
개인 정적 T[] CloneArray<T>( T[] 소스 )
{
var dest = new T[source.Length];
Array.Copy( source, dest, source.Length );
목적지를 반환;
}
}
공개 클래스
{
공개 문자열 FirstName
{
얻다;
세트;
}
공개 문자열 성
{
얻다;
세트;
}
공개 정수 ID
{
얻다;
세트;
}
}
공개 클래스 PersonComparer : IComparer<Person>
{
공개 int 비교(사람 x, 사람 y)
{
x.ID - y.ID를 반환합니다.
}
}
}
테스트 결과는 실제로 릴리스 컴파일 모드에서도 Enumerable.OrderBy의 확실한 이점을 보여줍니다.
이는 원칙적으로 불가능하므로 어딘가에서 불공정한 경쟁이 있을 수밖에 없습니다.
전체 코드를 다시 검토하고 두 테스트 코드의 위치를 조정했습니다.
CodeTimer.Time( "SortWithLinq", 100, () => SortWithLinq( CloneArray( 배열 ) ) );
CodeTimer.Time( "SortWithCustomComparer", 100, () => SortWithCustomComparer( CloneArray( array ) ) );
물론 이것은 아무런 효과가 없습니다. 둘째, 이 비교 인스턴스가 여러 번(실제로는 100번만) 생성된 것을 확인하여 다음과 같이 최적화했습니다.
private static PersonComparer 비교자 = new PersonComparer();
개인 정적 무효 SortWithCustomComparer(Person[] 배열)
{
Array.Sort(배열, 비교자);
}
결과에는 개선이 없습니다.
따라서 비교자 구현에만 집중할 수 있습니다.
공개 클래스 PersonComparer : IComparer<Person>
{
공개 int 비교(사람 x, 사람 y)
{
x.ID - y.ID를 반환합니다.
}
}
이는 공정한 접근 방식은 아니지만 성능 문제를 일으키지 않아야 합니다. 다음 형식으로 변경하세요.
공개 int 비교(사람 x, 사람 y)
{
return x.ID.CompareTo(y.ID);
}
여전히 성능 향상은 없지만 약간 감소합니다(이는 정상입니다).
우리가 막다른 골목에 있는 것 같나요? 사실 우리는 진실에 매우 가깝습니다. 사실 제목은 이미 답을 발표했습니다. 문제는 person.ID가 속성이기 때문에 여기서 메소드는 실제로 세 번 호출됩니다.
따라서 ID를 필드로 변경하면 예상한 성능 결과에 가까워집니다.
그렇다면 LINQ가 여기서 이점을 갖는 이유는 무엇입니까? 아, LINQ의 정렬 원리를 이해한다면 LINQ가 Person.get_ID에 대한 많은 호출을 저장할 수 있다고 생각하는 것은 어렵지 않습니다. 이 분석은 Daniel의 다음 기사에서 자세히 설명될 것이라고 생각하므로 여기서는 설명하지 않겠습니다.
따라서 결론을 내릴 수 있지만 여전히 LINQ가 특정 환경에서 성능 이점을 갖고 있다는 것은 다소 놀라운 일입니다. 하지만 제가 말하고 싶은 것은 실제로 이 결론에 영향을 미치는 요소가 많다는 것입니다. 일반적으로 LINQ가 성능 이점을 가져야 한다거나 Array.Sort가 성능 이점을 가져야 한다고 말할 수는 없습니다.