Vor ein paar Tagen wurden N Artikel veröffentlicht, die besagten, dass c#/.net zu langsam sei und forderten, dass einige Funktionen von c#/.net gelöscht werden müssten.
Unabhängig von diesen Artikeln scheint es eine von der Branche anerkannte eiserne Regel zu sein, dass c#/.net langsam ist. Egal wie jeder beweist, dass c#/.net nicht viel langsamer als c++ ist, die Leistung auf Anwendungsebene ist immer noch so langsam .
Wo ist c#/.net langsam?
Leider werden die meisten C#-Programme von den meisten Programmierern verlangsamt. Diese Schlussfolgerung ist vielleicht nicht leicht zu akzeptieren, aber sie ist weit verbreitet.
String-Operationen
Fast alle Programme verfügen über String-Operationen, und mindestens 90 % von ihnen müssen Groß-/Kleinschreibungsvergleiche ignorieren. Mindestens die Hälfte von ihnen hat einen ähnlichen Code wie diesen:
if (str1.ToUpper() == str2.ToUpper())
Oder die ToLower-Version, ich habe sogar gesehen, dass es ein Web HttpModule gibt, das sagt:
for (int i = 0; i < strs.Count; i++)
if (value.ToUpper() == strs[i].ToUpper())
//......
Denken Sie darüber nach, dass jedes Mal, wenn eine Seite angefordert wird, ein solcher Code ausgeführt werden muss, um Zeichenfolgeninstanzen in großen Abschnitten zu erstellen. Noch übertriebener ist es, dass einige Leute sagen, dass dadurch Raum gegen Zeit ausgetauscht wird. . .
Leistungstests
Wenn gesagt wird, dass diese Methode langsam ist, geben manche Leute das vielleicht nicht zu und denken, dass dies die beste Methode ist. Daher müssen wir hier spezifische Tests verwenden, um die Tatsache zu beweisen.
Bereiten Sie zunächst eine Methode zum Testen der Leistung vor:
privates statisches 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);
TResult result = default(TResult);
Stoppuhr sw = Stopwatch.StartNew();
for (int i = 0; i < Schleife; i++)
{
Ergebnis = 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());
Ergebnis zurückgeben;
}
Bereiten Sie dann eine Heap-Zeichenfolge vor:
private statische Liste<string> CreateStrings()
{
List<string> strs = new List<string>(10000);
char[] chs = new char[3];
for (int i = 0; i < 10000; i++)
{
int j = i;
for (int k = 0; k < chs.Length; k++)
{
chs[k] = (char)('a' + j % 26);
j = j / 26;
}
strs.Add(new string(chs));
}
return strs;
}
Dann werfen wir einen Blick auf die Implementierung von ToUpper:
private static bool ImplementByToUpper(List<string> strs, string value)
{
for (int i = 0; i < strs.Count; i++)
if (value.ToUpper() == strs[i].ToUpper())
return true;
return false;
}
Bereiten Sie abschließend die Hauptmethode vor:
List<string> strs = CreateStrings();
boolisches Ergebnis;
Console.WriteLine("Use ImplementByToUpper");
result = MeasurePerformance(s => ImplementByToUpper(strs, s), "yZh", 1000);
Console.WriteLine("result is " + result.ToString());
Console.ReadLine();
Werfen wir einen Blick auf die Ausführungsergebnisse:
Verwenden Sie ImplementByToUpper
2192 ms
GC 0:247
GC 1:0
GC 2:0
Das Ergebnis ist wahr
Machen wir einen Vergleichstest und verwenden string.Equals zum Testen:
private static bool ImplementByStringEquals(List<string> strs, string value)
{
for (int i = 0; i < strs.Count; i++)
if (string.Equals(value, strs[i], StringComparison.CurrentCultureIgnoreCase))
return true;
return false;
}
Werfen wir einen Blick auf die Ausführungsergebnisse:
Verwenden Sie ImplementByStringEquals
1117 ms
GC 0:0
GC 1:0
GC 2:0
Das Ergebnis ist wahr
Im Vergleich dazu ist die Verwendung von ToUpper doppelt so langsam und weist viele Müllobjekte der Generation 0 auf. Diejenigen, die behaupten, Raum gegen Zeit einzutauschen, können darüber nachdenken: Was haben sie im Austausch für Raum bekommen? Negative Zeit?
Verwendung der Wörterbuchklasse
Wenn wir mit dem String-Szenario fortfahren, denken einige Leute vielleicht darüber nach, Hash-Tabellen und andere ähnliche Strukturen zu verwenden, um die Geschwindigkeit zu erhöhen. Ja, das ist eine gute Idee, aber Hash-Tabellen sind nicht immer die beste Lösung. Machen wir einen Test:
private static bool ImplementByHashSet(List<string> strs, string value)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
return set.Contains(value);
}
Schauen Sie sich die Ausführungsergebnisse an:
Verwenden Sie ImplementByHashSet
5114 ms
GC 0:38
GC 1:38
GK 2:38
Das Ergebnis ist wahr
Überraschenderweise ist die Geschwindigkeit mehr als doppelt so langsam wie bei der Verwendung von ToUpper, und der Müll der zweiten Generation wird ebenfalls 38 Mal gesammelt (bei der Ausführung der Müllsammlung der zweiten Generation werden die Müllsammlung der ersten und der Nullgeneration erzwungen).
Die Idee, eine Hash-Tabelle oder ähnliches zu verwenden, um den Prozess zu beschleunigen, ist jedoch eine sehr richtige Idee, aber die Voraussetzung ist, dass die Hash-Tabelle selbst zwischengespeichert werden kann, zum Beispiel:
private static Func<string, bool> ImplementByHashSet2(List<string> strs)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
return set.Contains;
}
Ändern Sie dann die Hauptmethode wie folgt:
Console.WriteLine("Use ImplementByHashSet2");
result = MeasurePerformance(s =>
{
var f = ImplementByHashSet2(strs);
bool ret = false;
for (int i = 0; i < 1000; i++)
{
ret = f(s);
}
return ret;
}, "yZh", 1);
Console.WriteLine("result is " + result.ToString());
Console.ReadLine();
Werfen wir einen Blick auf die Ergebnisse:
Verwenden Sie ImplementByHashSet2
6ms
GC 0:0
GC 1:0
GC 2:0
Das Ergebnis ist wahr
Die Leistung ist dramatisch gestiegen.
Mehr
Was verlangsamt c#/.net? Einfach ausgedrückt: unnötige Erstellung von Objekten, unnötige Synchronisierung, ineffiziente Methoden zur Schleifenausführung (z. B. Reflektion, die von Firelong kritisiert wurde, aber MS erlaubt die Verwendung von Invoke in der Schleife nicht), Verwendung ineffizienter Datenstrukturen und Algorithmen (siehe Sehen Sie sich die erstaunliche Leistung einer ähnlichen Struktur der Hash-Tabelle beim Caching an, und Sie werden den Unterschied kennen.)
Der niedrige Schwellenwert von c#/.net trägt in gewissem Maße dazu bei, mehr Programmierer für c#/.net zu gewinnen, aber er reduziert auch die Codeebene des gesamten c#/.net-Programms erheblich, was wirklich beeindruckend ist.
Vergessen Sie schließlich nicht, dass die Leistung eines Systems nicht vom Teil des Systems mit der besten Leistung bestimmt wird, sondern vom Teil des Systems mit der schlechtesten Leistung. Ausgestattet mit einem 16-g-Speicher, einer 100-t-Festplatte und einer erstklassigen Grafikkarte, aber ohne 386-CPU, entspricht die Leistung dieses Computers der Leistung des 386. Unabhängig davon, wie gut C # / .net ist, ist die Leistung des geschriebenen Programms natürlich schlecht, wenn die Fähigkeiten des Programmierers schlecht sind.