Il y a quelques jours, N articles ont été publiés affirmant que c#/.net était trop lent et exigeant la suppression de certaines fonctionnalités de c#/.net.
Indépendamment de ces articles, il semble y avoir une règle absolue reconnue par l'industrie selon laquelle c#/.net est lent. Même si tout le monde prouve que c#/.net n'est pas beaucoup plus lent que c++, les performances au niveau de l'application sont toujours aussi lentes. .
Alors, où est lent c#/.net ?
Malheureusement, la plupart des programmes C# sont ralentis par la plupart des programmeurs. Cette conclusion n’est peut-être pas facile à accepter, mais elle est répandue.
Opérations sur les chaînes
Presque tous les programmes ont des opérations String, et au moins 90 % d'entre eux doivent ignorer les comparaisons de cas. Vérifiez le code. Au moins la moitié d'entre eux ont un code similaire à celui-ci :
si (str1.ToUpper() == str2.ToUpper())
Ou la version ToLower, j'ai même vu qu'il y a un Web HttpModule qui dit :
pour (int i = 0; i < strs.Count; i++)
if (value.ToUpper() == strs[i].ToUpper())
//...
Pensez-y, chaque fois qu'une page est demandée, un tel morceau de code doit être exécuté pour créer des instances de chaîne dans de grands fragments. Ce qui est encore plus exagéré, c'est que certaines personnes disent que cela échange de l'espace contre du temps. . .
Tests de performances
Si l'on dit que cette méthode est lente, certaines personnes peuvent ne pas l'admettre et penser que c'est la meilleure méthode, nous devons donc ici utiliser des tests spécifiques pour prouver le fait.
Préparez d’abord une méthode pour tester les performances :
TResult statique privé 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);
Résultat TResult = par défaut (TResult);
Chronomètre sw = Stopwatch.StartNew();
pour (int i = 0; i < boucle; i++)
{
résultat = func(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());
renvoyer le résultat ;
}
Préparez ensuite une chaîne de tas :
Liste statique privée<string> CreateStrings()
{
Liste<string> strs = new List<string>(10000);
char[] chs = nouveau char[3];
pour (int i = 0; i < 10000; i++)
{
int j = je;
pour (int k = 0; k < chs.Length; k++)
{
chs[k] = (char)('a' + j % 26);
j = j/26 ;
}
strs.Add(nouvelle chaîne(chs));
}
renvoie les chaînes ;
}
Jetons ensuite un coup d'œil à l'implémentation de ToUpper :
bool statique privé ImplementByToUpper(List<string> strs, valeur de chaîne)
{
pour (int i = 0; i < strs.Count; i++)
if (value.ToUpper() == strs[i].ToUpper())
renvoie vrai ;
renvoie faux ;
}
Préparez enfin la méthode principale :
Liste<string> strs = CreateStrings();
résultat booléen ;
Console.WriteLine("Utiliser ImplementByToUpper");
result = MeasurePerformance(s => ImplementByToUpper(strs, s), "yZh", 1000);
Console.WriteLine("le résultat est " + result.ToString());
Console.ReadLine();
Jetons un coup d'œil aux résultats de l'exécution :
Utiliser ImplementByToUpper
2192 ms
CG 0:247
CG 1:0
CG 2:0
le résultat est vrai
Faisons un test comparatif et utilisons string.Equals pour tester :
bool statique privé ImplementByStringEquals (List<string> strs, valeur de chaîne)
{
pour (int i = 0; i < strs.Count; i++)
if (string.Equals(value, strs[i], StringComparison.CurrentCultureIgnoreCase))
renvoie vrai ;
renvoie faux ;
}
Jetons un coup d'œil aux résultats de l'exécution :
Utiliser ImplementByStringEquals
1117 ms
CG 0:0
CG 1:0
CG 2:0
le résultat est vrai
En comparaison, l’utilisation de ToUpper est deux fois plus lente et contient beaucoup d’objets indésirables de génération 0. Ceux qui prétendent échanger de l’espace contre du temps peuvent réfléchir à cela. Qu’ont-ils obtenu en échange de l’espace ? Temps négatif ?
Utilisation de la classe dictionnaire
En continuant avec le scénario de chaîne, certaines personnes peuvent penser à utiliser des tables de hachage et d'autres structures similaires pour accélérer. Oui, c'est une bonne idée, mais les tables de hachage ne sont pas toujours la meilleure solution. Pourquoi n'y croyez-vous pas ? Faisons un test :
bool statique privé ImplementByHashSet (List<string> strs, valeur de chaîne)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
return set.Contains(valeur);
}
Jetez un œil aux résultats de l'exécution :
Utiliser ImplementByHashSet
5114ms
CG 0:38
CG 1:38
CG 2:38
le résultat est vrai
Étonnamment, la vitesse est plus de deux fois plus lente que l'utilisation de ToUpper, et les déchets de deuxième génération sont également collectés 38 fois (lors de l'exécution du garbage collection de deuxième génération, le garbage collection de première génération et de génération zéro sera forcé).
Cependant, l'idée d'utiliser une table de hachage ou similaire pour accélérer le processus est une idée très correcte, mais le principe est que la table de hachage elle-même peut être mise en cache, par exemple :
private static Func<string, bool> ImplementByHashSet2(List<string> strs)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
renvoie set.Contains ;
}
Modifiez ensuite la méthode main pour :
Console.WriteLine("Utiliser ImplementByHashSet2");
résultat = MesurePerformance(s =>
{
var f = ImplementByHashSet2(strs);
bool ret = faux ;
pour (int i = 0; i < 1000; i++)
{
ret = f(s);
}
retour à la retraite;
}, "yZh", 1);
Console.WriteLine("le résultat est " + result.ToString());
Console.ReadLine();
Jetons un coup d'œil aux résultats :
Utiliser ImplementByHashSet2
6 ms
CG 0:0
CG 1:0
CG 2:0
le résultat est vrai
Les performances ont considérablement augmenté.
Plus
Qu'est-ce qui ralentit c#/.net ? Pour faire simple : création inutile d'objets, synchronisation inutile, méthodes d'exécution de boucle inefficaces (comme la réflexion qui a été critiquée par Firelong, mais ms ne vous permet pas d'utiliser Invoke dans la boucle), utilisation de structures de données et d'algorithmes inefficaces (regardez aux performances étonnantes d'une structure similaire de table de hachage dans le cas de la mise en cache, et vous connaîtrez la différence)
Le faible seuil de c#/.net contribue dans une certaine mesure à attirer davantage de programmeurs vers c#/.net, mais il réduit également considérablement le niveau de code de l'ensemble du programme c#/.net, ce qui est vraiment impressionnant.
Enfin, n'oubliez pas que les performances d'un système ne sont pas déterminées par la partie du système la plus performante, mais par la partie du système la moins performante. Équipé d'une mémoire de 16 Go, d'un disque dur de 100 To et d'une carte graphique de premier ordre, mais sans processeur 386, les performances de cet ordinateur sont les performances du 386. De même, quelle que soit la qualité de C#/.net, si les compétences du programmeur sont médiocres, les performances du programme écrit seront naturellement médiocres.