Diz-se que o irmão Jiang Min conduziu um teste de desempenho suficientemente rigoroso nos métodos Array.Sort e Enumerable.OrderBy há algum tempo. A conclusão parece ser inconsistente com a teoria e as expectativas, mas esta conclusão foi medida num ambiente relativamente rigoroso, o que também despertou o interesse de especialistas. Também testei em minha máquina. Claro, resolvi primeiro alguns códigos irrelevantes:
usando o sistema;
usando System.Collections.Generic;
usando System.Diagnostics;
usando System.Linq;
usando System.Runtime.InteropServices;
usando System.Threading;
namespace Exame11
{
classe estática pública CodeTimer
{
público estático vazio Inicializar()
{
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
Tempo( "", 1, () => { } );
}
public static void Time (nome da string, iteração interna, ação Action)
{
if (String.IsNullOrEmpty(nome)) retorno;
// aquecimento
Ação();
//1.
ConsoleColor currentForeColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(nome);
//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.
Cronômetro = new Cronômetro();
assistir.Iniciar();
ciclolongoCount = GetCycleCount();
for (int i = 0; i < iteração; i++ ) action();
ao longo de cpuCycles = GetCycleCount() - cycleCount;
assistir.Parar();
//4.
Console.ForegroundColor = currentForeColor;
Console.WriteLine( "tTempo Decorrido:t" + watch.ElapsedMilliseconds.ToString( "N0" ) + "ms" );
Console.WriteLine( "tCiclos de CPU:t" + cpuCycles.ToString( "N0" ) );
//5.
for (int i = 0; i <= GC.MaxGeneration; i++ )
{
contagem int = GC.CollectionCount(i) - gcCounts[i];
Console.WriteLine( "tGen " + i + ": tt" + contagem );
}
Console.WriteLine();
}
estático privado ao longo de GetCycleCount()
{
ciclo longoCount = 0;
QueryThreadCycleTime(GetCurrentThread(), ref cycleCount);
retornar contagem de ciclos;
}
[DllImport("kernel32.dll" )]
[retorno: MarshalAs(UnknownType.Bool)]
static extern bool QueryThreadCycleTime (IntPtr threadHandle, ref ulong cycleTime);
[DllImport("kernel32.dll" )]
static extern IntPtr GetCurrentThread();
}
programa de aula
{
static void Principal(string[] args)
{
var aleatório = new Random(DateTime.Now.Millisecond);
var array = Enumerable.Repeat( 0, 50000 ).Select( _ => new Person { ID = random.Next() } ).ToArray();
//Programa Lao Zhao
CodeTimer.Initialize();
CodeTimer.Time("SortWithCustomComparer", 500, () => SortWithCustomComparer( CloneArray( array ) ) );
CodeTimer.Time("SortWithLinq", 500, () => SortWithLinq( CloneArray( array ) ) );
Console.ReadLine();
}
private static void SortWithCustomComparer (Person[] array)
{
Array.Sort(array, new PersonComparer() );
}
privado estático void SortWithLinq (Person[] array)
{
var ordenado =
(de pessoa na matriz
encomendar por pessoa.ID
selecione pessoa ).ToList();
}
privado estático T[] CloneArray<T>( T[] fonte)
{
var destino = novo T[source.Length];
Array.Copy(fonte, destino, fonte.Length);
destino de retorno;
}
}
classe pública Pessoa
{
string pública Nome
{
pegar;
definir;
}
string pública Sobrenome
{
pegar;
definir;
}
ID interno público
{
pegar;
definir;
}
}
classe pública PersonComparer: IComparer<Person>
{
public int Comparar (Pessoa x, Pessoa y)
{
retornar x.ID - y.ID;
}
}
}
Os resultados do teste mostram de fato as vantagens óbvias do Enumerable.OrderBy, mesmo no modo de compilação Release.
Em princípio, isso é impossível, por isso deve haver alguma concorrência desleal em algum lugar.
Revisitei todo o código e ajustei a posição dos dois códigos de teste:
CodeTimer.Time("SortWithLinq", 100, () => SortWithLinq( CloneArray( array ) ) );
CodeTimer.Time("SortWithCustomComparer", 100, () => SortWithCustomComparer( CloneArray( array ) ) );
Claro, isso não terá nenhum efeito. Em segundo lugar, descobri que esta instância do comparador foi criada várias vezes (na verdade, apenas 100 vezes), então otimizei-a da seguinte forma:
private static PersonComparer comparador = new PersonComparer();
private static void SortWithCustomComparer (Person[] array)
{
Array.Sort(matriz, comparador);
}
Não há melhora nos resultados.
Portanto, só posso me concentrar na implementação do comparador:
classe pública PersonComparer: IComparer<Person>
{
public int Comparar (Pessoa x, Pessoa y)
{
retornar x.ID - y.ID;
}
}
Esta não é uma abordagem justa, mas não deve causar problemas de desempenho. Altere-a para o seguinte formato:
public int Comparar (Pessoa x, Pessoa y)
{
retornar x.ID.CompareTo(y.ID);
}
Ainda não há melhora no desempenho, mas sim uma ligeira diminuição (isso é normal).
Parece que estamos em um impasse? Na verdade, estamos muito perto da verdade. Na verdade, o título já anunciou a resposta. Como o problema está no fato de person.ID ser um atributo, o método é chamado três vezes aqui.
Portanto, basta alterar o ID para um campo e os resultados de desempenho ficarão próximos do esperado.
Então, por que o LINQ tem uma vantagem aqui? Ah, se você entende o princípio de classificação do LINQ, não é difícil pensar que o LINQ pode salvar muitas chamadas para Person.get_ID. Acredito que essa análise será elaborada no próximo artigo do Daniel, por isso não vou me exibir aqui.
Portanto, a conclusão pode ser tirada, mas ainda é um pouco surpreendente que o LINQ tenha vantagens de desempenho em determinados ambientes. Mas o que quero dizer é que na verdade existem muitos fatores que afetarão essa conclusão. Não podemos dizer de maneira geral que o LINQ deve ter vantagens de desempenho ou o Array.Sort deve ter vantagens de desempenho.