Говорят, что брат Цзян Минь некоторое время назад провел достаточно строгий тест производительности методов Array.Sort и Enumerable.OrderBy. Вывод вроде бы не соответствует теории и ожиданиям, но этот вывод был сделан в относительно строгой среде, что также вызвало интерес у экспертов. Я также протестировал его на своей машине. Конечно, сначала я разобрался с некоторыми ненужными кодами:
использование системы;
использование System.Collections.Generic;
использование System.Diagnostics;
используя System.Linq;
использование System.Runtime.InteropServices;
использование System.Threading;
пространство имен Экзамен 11
{
публичный статический класс CodeTimer
{
публичная статическая пустота Initialize()
{
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
Время( "", 1, () => { } );
}
public static void Time (имя строки, целая итерация, действие действия)
{
if ( String.IsNullOrEmpty(name) ) return;
// разогревать
действие();
// 1.
ConsoleColor currentForeColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(имя);
// 2.
GC.Collect( GC.MaxGeneration, GCCollectionMode.Forced );
int[] gcCounts = новый int[GC.MaxGeneration + 1];
for (int i = 0; i <= GC.MaxGeneration; i++)
{
gcCounts[i] = GC.CollectionCount(i);
}
// 3.
Секундомер смотреть = новый секундомер();
смотреть.Старт();
ulong CycleCount = GetCycleCount();
for (int i = 0; i < итерация; i++) action();
ulong cpuCycles = GetCycleCount() -cycleCount;
смотреть.Стоп();
// 4.
Console.ForegroundColor = currentForeColor;
Console.WriteLine( "tTime Elapsed:t" + watch.ElapsedMilliсекунды.ToString( "N0") + "ms" );
Console.WriteLine( "tCPU Cycles: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 );
}
Консоль.WriteLine();
}
частный статический ulong GetCycleCount()
{
длинный циклCount = 0;
QueryThreadCycleTime( GetCurrentThread(), refcycleCount);
возврат циклаCount;
}
[DllImport("kernel32.dll" )]
[возврат: MarshalAs(UnmanagedType.Bool)]
static extern bool QueryThreadCycleTime (IntPtr threadHandle, ref ulong CycleTime);
[DllImport("kernel32.dll" )]
статический extern IntPtr GetCurrentThread();
}
классная программа
{
static void Main(string[]args)
{
вар случайный = новый случайный (DateTime.Now.Milli Second);
var array = Enumerable.Repeat(0, 50000).Select( _ => новый человек {ID = random.Next() }).ToArray();
//Программа Лао Чжао
КодТаймер.Инициализировать();
CodeTimer.Time( "SortWithCustomComparer", 500, () => SortWithCustomComparer( CloneArray( array ) ) );
CodeTimer.Time( "SortWithLinq", 500, () => SortWithLinq( CloneArray( array ) ) );
Консоль.ReadLine();
}
Private static void SortWithCustomComparer (массив Person[])
{
Array.Sort(массив, новый PersonComparer());
}
Private static void SortWithLinq (массив Person[])
{
вар отсортировано =
(от человека в массиве
заказатьпо персоне.ID
выберите человека ).ToList();
}
частный статический T[] CloneArray<T>(источник T[])
{
вар dest = новый T[source.Length];
Array.Copy(источник, место назначения, источник.Длина);
место возврата;
}
}
общественный класс Человек
{
общедоступная строка Имя
{
получать;
набор;
}
общедоступная строка Фамилия
{
получать;
набор;
}
общедоступный внутренний идентификатор
{
получать;
набор;
}
}
общедоступный класс PersonComparer: IComparer<Person>
{
public int Compare(Человек x, Человек y)
{
вернуть x.ID - y.ID;
}
}
}
Результаты тестов действительно показывают очевидные преимущества Enumerable.OrderBy даже в режиме компиляции Release.
В принципе это невозможно, значит, где-то должна быть недобросовестная конкуренция.
Я пересмотрел весь код и скорректировал положение двух тестовых кодов:
CodeTimer.Time( "SortWithLinq", 100, () => SortWithLinq( CloneArray( array ) ) );
CodeTimer.Time( "SortWithCustomComparer", 100, () => SortWithCustomComparer( CloneArray( array ) ) );
Конечно, это не даст никакого эффекта. Во-вторых, я обнаружил, что этот экземпляр компаратора создавался несколько раз (на самом деле только 100 раз), поэтому оптимизировал его следующим образом:
частный статический компаратор PersonComparer = новый PersonComparer();
Private static void SortWithCustomComparer (массив Person[])
{
Array.Sort(массив, средство сравнения);
}
Улучшения результатов нет.
Поэтому я могу сосредоточиться только на реализации компаратора:
общедоступный класс PersonComparer: IComparer<Person>
{
public int Compare(Человек x, Человек y)
{
вернуть x.ID - y.ID;
}
}
Это несправедливый подход, но он не должен вызывать проблем с производительностью. Измените его на следующий вид:
public int Compare(Человек x, Человек y)
{
вернуть x.ID.CompareTo(y.ID);
}
Улучшения производительности по-прежнему нет, но есть небольшое снижение (это нормально).
Кажется, мы в тупике? На самом деле мы очень близки к истине. На самом деле, в заголовке уже объявлен ответ. Поскольку проблема заключается в том, что person.ID является атрибутом, метод фактически вызывается здесь три раза.
Поэтому просто измените идентификатор на поле, и результаты производительности будут близки к ожидаемым.
Так почему же у LINQ здесь преимущество? Ах, если вы понимаете принцип сортировки LINQ, нетрудно подумать, что LINQ может сэкономить множество вызовов Person.get_ID. Я думаю, что этот анализ будет подробно описан в следующей статье Дэниела, поэтому не буду здесь хвастаться.
Итак, вывод можно сделать, но он все же немного удивляет. LINQ действительно имеет преимущества в производительности в определенных средах. Эта теория доказана. Но я хочу сказать, что на самом деле на этот вывод влияет множество факторов. Мы действительно не можем в общих чертах сказать, что LINQ должен иметь преимущества в производительности, а Array.Sort должен иметь преимущества в производительности.