Se dice que el hermano Jiang Min realizó una prueba de rendimiento suficientemente rigurosa en los métodos Array.Sort y Enumerable.OrderBy hace un tiempo. La conclusión parece contradecir la teoría y las expectativas, pero se midió en un entorno relativamente riguroso, lo que también despertó el interés de los expertos. También lo probé en mi máquina. Por supuesto, primero resolví algunos códigos irrelevantes:
usando Sistema;
usando System.Collections.Generic;
utilizando System.Diagnostics;
usando System.Linq;
usando System.Runtime.InteropServices;
usando System.Threading;
espacio de nombres Examen11
{
clase estática pública CodeTimer
{
Inicializar vacío estático público ()
{
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
Hora( "", 1, () => { } );
}
Tiempo vacío estático público (nombre de cadena, iteración de inserción, acción de acción)
{
si (String.IsNullOrEmpty(nombre)) regresa;
// calentamiento
acción();
// 1.
ConsoleColor currentForeColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine( nombre );
// 2.
GC.Collect( GC.MaxGeneration, GCCollectionMode.Forced );
int[] gcCounts = nuevo int[GC.MaxGeneration + 1];
para (int i = 0; i <= GC.MaxGeneration; i++)
{
gcCounts[i] = GC.CollectionCount(i);
}
// 3.
Reloj cronómetro = nuevo Cronómetro();
reloj.Inicio();
ulong cicloCount = GetCycleCount();
para (int i = 0; i < iteración; i++) acción();
ulong cpuCycles = GetCycleCount() - cicloCount;
reloj.Detener();
// 4.
Console.ForegroundColor = currentForeColor;
Console.WriteLine( "tTiempo transcurrido:t" + watch.ElapsedMillisegundos.ToString( "N0" ) + "ms" );
Console.WriteLine( "tCPU Cycles:t" + cpuCycles.ToString( "N0" ) );
// 5.
para (int i = 0; i <= GC.MaxGeneration; i++)
{
int recuento = GC.CollectionCount(i) - gcCounts[i];
Console.WriteLine( "tGen " + i + ": tt" + recuento);
}
Consola.WriteLine();
}
estático privado durante GetCycleCount()
{
ciclo largoCount = 0;
QueryThreadCycleTime (GetCurrentThread(), referencia cicloCount);
retorno cicloCount;
}
[DllImport ("kernel32.dll")]
[regreso: MarshalAs(UnmanagedType.Bool)]
bool externo estático QueryThreadCycleTime (IntPtr threadHandle, ref ulong CycleTime);
[DllImport ("kernel32.dll")]
estático externo IntPtr GetCurrentThread();
}
programa de clase
{
static void Principal (cadena [] argumentos)
{
var aleatorio = nuevo Aleatorio(DateTime.Now.Millisegundo);
var matriz = Enumerable.Repeat( 0, 50000 ).Select( _ => nueva Persona { ID = random.Next() } ).ToArray();
//Programa Lao Zhao
CodeTimer.Initialize();
CodeTimer.Time( "SortWithCustomComparer", 500, () => SortWithCustomComparer( CloneArray(matriz) ) );
CodeTimer.Time( "SortWithLinq", 500, () => SortWithLinq( CloneArray(matriz) ) );
Consola.ReadLine();
}
vacío estático privado SortWithCustomComparer (matriz Persona [])
{
Array.Sort(matriz, nuevo PersonComparer());
}
vacío estático privado SortWithLinq (matriz Persona [])
{
var ordenado =
(de persona en matriz
ordenar por persona.ID
seleccionar persona ).ToList();
}
privado estático T[] CloneArray<T>( T[] fuente )
{
var destino = nuevo T[fuente.Longitud];
Array.Copy(fuente, destino, fuente.Longitud);
destino de regreso;
}
}
persona de clase pública
{
cadena pública Nombre
{
conseguir;
colocar;
}
cadena pública Apellido
{
conseguir;
colocar;
}
identificación interna pública
{
conseguir;
colocar;
}
}
clase pública PersonComparer: IComparer<Persona>
{
public int Comparar (Persona x, Persona y)
{
devolver x.ID - y.ID;
}
}
}
De hecho, los resultados de la prueba muestran las ventajas obvias de Enumerable.OrderBy, incluso en el modo de compilación Release.
En principio, esto es imposible, por lo que debe haber alguna competencia desleal en alguna parte.
Revisé todo el código y ajusté la posición de los dos códigos de prueba:
CodeTimer.Time( "SortWithLinq", 100, () => SortWithLinq( CloneArray(matriz) ) );
CodeTimer.Time( "SortWithCustomComparer", 100, () => SortWithCustomComparer( CloneArray(matriz) ) );
Por supuesto, esto no tendrá ningún efecto. En segundo lugar, descubrí que esta instancia de comparación se creó varias veces (en realidad, solo 100 veces), así que la optimicé de la siguiente manera:
comparador de PersonComparer estático privado = nuevo PersonComparer();
vacío estático privado SortWithCustomComparer (matriz Persona [])
{
Array.Sort(matriz, comparador);
}
No hay mejora en los resultados.
Entonces solo puedo concentrarme en la implementación del comparador:
clase pública PersonComparer: IComparer<Persona>
{
public int Comparar (Persona x, Persona y)
{
devolver x.ID - y.ID;
}
}
Este no es un enfoque justo, pero no debería causar problemas de rendimiento. Cámbielo al siguiente formato:
public int Comparar (Persona x, Persona y)
{
devolver x.ID.CompareTo (y.ID);
}
Todavía no hay mejora en el rendimiento, sino una ligera disminución (esto es normal).
¿Parece que estamos en un punto muerto? De hecho, estamos muy cerca de la verdad. De hecho, el título ya ha anunciado la respuesta. Debido a que el problema radica en que person.ID es un atributo, el método se llama tres veces aquí.
Simplemente cambie el ID a un campo y los resultados de rendimiento serán similares a los esperados.
Entonces, ¿por qué LINQ tiene una ventaja aquí? Ah, si comprende el principio de clasificación de LINQ, no es difícil pensar que LINQ puede guardar muchas llamadas a Person.get_ID. Creo que este análisis se desarrollará en el próximo artículo de Daniel, así que no lo presumiré aquí.
Entonces se puede sacar la conclusión, pero aún es un poco sorprendente. LINQ tiene ventajas de rendimiento en ciertos entornos. Esta teoría está establecida. Pero lo que quiero decir es que en realidad hay muchos factores que afectarán esta conclusión. Realmente no podemos decir de manera general que LINQ debe tener ventajas de rendimiento o Array.Sort debe tener ventajas de rendimiento.