Há alguns dias, N artigos foram publicados dizendo que c#/.net é muito lento e exige que alguns recursos do c#/.net sejam excluídos.
Independentemente desses artigos, parece ser uma regra rígida reconhecida pela indústria que c#/.net é lento. Não importa como todos provem que c#/.net não é muito mais lento que c++, o desempenho no nível do aplicativo ainda é muito lento. .
Então, onde está o c#/.net lento?
Infelizmente, a maioria dos programas C# são retardados pela maioria dos programadores. Esta conclusão pode não ser fácil de aceitar, mas é generalizada.
Operações de string
Quase todos os programas possuem operações String e pelo menos 90% deles precisam ignorar comparações de casos. Verifique o código. Pelo menos metade deles possui código semelhante a este:
if (str1.ToUpper() == str2.ToUpper())
Ou a versão ToLower, até vi que existe um Web HttpModule que diz:
for (int i = 0; i < strs.Count; i++)
if (valor.ToUpper() == strs[i].ToUpper())
//...
Pense nisso, toda vez que uma página é solicitada, tal código deve ser executado para criar instâncias de string em grandes extensões. O que é ainda mais exagerado é que algumas pessoas dizem que isso é uma troca de espaço por tempo. . .
Teste de desempenho
Se este método for considerado lento, algumas pessoas podem não admitir e pensar que este é o melhor método, então aqui precisamos usar testes específicos para comprovar o fato.
Primeiro prepare um método para testar o desempenho:
private static TResult MeasurePerformance<TArg, TResult>(Func<TArg, TResult> func, TArg arg, int loop)
{
GC.Collect();
int gc0 = GC.CollectionCount(0);
int gc1 = GC.CollectionCount(1);
int gc2 = GC.CollectionCount(2);
Resultado TResult = padrão(TResult);
Cronômetro sw = Stopwatch.StartNew();
for (int i = 0; i <loop; i++)
{
resultado = função(arg);
}
Console.WriteLine(sw.ElapsedMilliseconds.ToString() + "ms");
Console.WriteLine("GC 0:" + (GC.CollectionCount(0) - gc0).ToString());
Console.WriteLine("GC 1:" + (GC.CollectionCount(1) - gc1).ToString());
Console.WriteLine("GC 2:" + (GC.CollectionCount(2) - gc2).ToString());
resultado de retorno;
}
Em seguida, prepare uma sequência de heap:
Lista estática privada<string> CreateStrings()
{
Lista<string>strs = new Lista<string>(10000);
char[] chs = novo char[3];
para (int i = 0; i < 10000; i++)
{
int j = eu;
for (int k = 0; k <chs.Length; k++)
{
chs[k] = (char)('a' + j % 26);
j = j/26;
}
strs.Add(nova string(chs));
}
retornar strings;
}
Então vamos dar uma olhada na implementação do ToUpper:
private static bool ImplementByToUpper(List<string> strs, valor da string)
{
for (int i = 0; i < strs.Count; i++)
if (valor.ToUpper() == strs[i].ToUpper())
retornar verdadeiro;
retornar falso;
}
Finalmente prepare o método principal:
Lista<string> strs = CreateStrings();
resultado booleano;
Console.WriteLine("Usar ImplementByToUpper");
resultado = MeasurePerformance(s => ImplementByToUpper(strs, s), "yZh", 1000);
Console.WriteLine("o resultado é " + resultado.ToString());
Console.ReadLine();
Vamos dar uma olhada nos resultados da execução:
Usar ImplementByToUpper
2192ms
GC 0:247
GC 1:0
CG 2:0
o resultado é verdadeiro
Vamos fazer um teste comparativo e usar string.Equals para testar:
private static bool ImplementByStringEquals(List<string> strs, valor da string)
{
for (int i = 0; i < strs.Count; i++)
if (string.Equals(valor, strs[i], StringComparison.CurrentCultureIgnoreCase))
retornar verdadeiro;
retornar falso;
}
Vamos dar uma olhada nos resultados da execução:
Usar ImplementByStringEquals
1117ms
GC 0:0
GC 1:0
GC 2:0
o resultado é verdadeiro
Em comparação, usar ToUpper é duas vezes mais lento e tem muitos objetos de lixo da geração 0. Aqueles que afirmam trocar espaço por tempo podem refletir sobre isso. Hora negativa?
Uso da classe de dicionário
Continuando com o cenário de string, algumas pessoas podem pensar em usar tabelas Hash e outras estruturas semelhantes para acelerar. Sim, é uma boa ideia, mas as tabelas Hash nem sempre são a melhor solução. Vamos fazer um teste:
private static bool ImplementByHashSet(List<string> strs, valor da string)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
retornar set.Contém(valor);
}
Dê uma olhada nos resultados da execução:
Usar ImplementByHashSet
5114ms
GC 0:38
GC 1:38
GC 2:38
o resultado é verdadeiro
Surpreendentemente, a velocidade é duas vezes mais lenta do que usar o ToUpper, e o lixo de segunda geração também é coletado 38 vezes (ao executar a coleta de lixo de segunda geração, a coleta de lixo de primeira geração e de geração zero será forçada).
Porém, a ideia de usar uma tabela Hash ou algo semelhante para agilizar o processo é uma ideia muito correta, mas a premissa é que a própria tabela Hash possa ser armazenada em cache, por exemplo:
private static Func<string, bool> ImplementByHashSet2(List<string> strs)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
retornar conjunto.Contém;
}
Em seguida, modifique o método principal para:
Console.WriteLine("Usar ImplementByHashSet2");
resultado = MedirDesempenho(s =>
{
var f = ImplementByHashSet2(strs);
bool ret = falso;
para (int i = 0; i < 1000; i++)
{
ret = f(s);
}
retornar ret;
}, "yZh", 1);
Console.WriteLine("o resultado é " + resultado.ToString());
Console.ReadLine();
Vamos dar uma olhada nos resultados:
Usar ImplementByHashSet2
6ms
GC 0:0
GC 1:0
GC 2:0
o resultado é verdadeiro
O desempenho aumentou dramaticamente.
Mais
O que retarda c#/.net? Simplificando: criação desnecessária de objetos, sincronização desnecessária, métodos ineficientes de execução de loop (como reflexão que foi criticada pelo firelong, mas o ms não permite usar Invoke no loop), uso de estruturas de dados e algoritmos ineficientes (veja no incrível desempenho de uma estrutura semelhante da tabela Hash no caso de cache, e você saberá a diferença)
O limite baixo de c#/.net ajuda a atrair mais programadores para c#/.net até certo ponto, mas também reduz muito o nível de código de todo o programa c#/.net, o que é realmente impressionante.
Finalmente, não esqueça que o desempenho de um sistema não é determinado pela parte do sistema com melhor desempenho, mas pela parte do sistema com pior desempenho. Equipado com memória de 16g, disco rígido de 100t, além de placa gráfica de primeira linha, mas sem CPU 386, o desempenho deste computador é o desempenho do 386. Da mesma forma, não importa quão bom seja C#/.net, se a habilidade do programador for ruim, o desempenho do programa escrito será naturalmente ruim.