Performace-Test: Java vs. C#
-
Ohne das jetzt beleidigend zu meinen: Hast Du auch in Betracht gezogen das Deine C# Performanceprobleme an Dir bzw. Deinem Wissenstand liegen könnten?
Es gab zu dem Thema Performanceunterschiede von Sprachen schon viele Studien und eines der Ergebnisse war, daß der Skill des Entwicklers einen viel größeren Einfluß auf die Performance hat als die Sprachunetrschiede.
Oder einfach gesagt, ein sehr guter C# Programmierer holt mehr Performance heraus als ein schlechter Java Progger und umgekehrt.
Gerade C# und Net Framework hat viele versteckte Performance-Fallen.
Billiges Beispiel: (Sollte eigendlich jeder hier kennen)
string tmp = "1"; tmp += "2"; tmp += "3"; tmp += "4"; tmp += "5"; tmp += "6";
StringBuilder temp; temp.Append("1"); temp.Append("2"); temp.Append("3"); temp.Append("4"); temp.Append("5"); temp.Append("6"); string ergebnis = temp.ToString();
Welche der beiden Varianten ist die performantere, warum und um welchen Faktor?
-
Hi loks,
loks schrieb:
Ohne das jetzt beleidigend zu meinen: Hast Du auch in Betracht gezogen das Deine C# Performanceprobleme an Dir bzw. Deinem Wissenstand liegen könnten?
Beleidigt fühl ich mich nicht, denn es ist häufig ein Problem, dass in einem Performance check, die eine (favorisierte) Seite laufzeittechnisch "optimiert" wird, die andere aber nicht (ähnlich deinem Beispiel).
Ich schrieb deswegen oben extra:...portierte ich es (im gleichen Programmierstiel)...
Was so viel heißt: Kein StringBuilder in C# und auch keinen für Java.
Ich habe mir wirklich Mühe gegeben, die Vorgehensweise bei beiden gleich zu lassen.
@~fricky & Mr Evil
Ich werde es demnächst Testen, und die Ergebnisse eintragen.Vielen Dank jetzt schon mal für eure Beiträge
Schang
-
schang schrieb:
Ich habe mir wirklich Mühe gegeben, die Vorgehensweise bei beiden gleich zu lassen.
Gerade das könnte aber ein Problem sein. Was wenn die Vorgehensweise rein zufällig in Java "Best Practice" ist und in C# "Worst Practice", es aber für C# einen alternativen Implementierungsweg gibt den man eigendlich benutzen sollte der viel schneller ist?
-
zu beachten ist auch das die darstellung der GUI zwischen Forms und WPF nicht gleich zu setzen ist - auch dort gibts performanceunterschiede
-
@lok:
ich bin in erster Linie C#-Entwickler. Deshalb würde ich für den am Anfang beschriebenen Fall dieses genau umdrehen, also: "Best Practise" für C# und "Worst Practise" für Java/SWT.@Mr Evil
zu beachten ist auch das die darstellung der GUI zwischen Forms und WPF nicht gleich zu setzen ist - auch dort gibts performanceunterschiede
ich habe WinForms für die Test-Anwendung verwendet. WPF kenn ich leider nicht. Wie sieht es denn dort mit Performance-Unterschiede zwischen WinForms und WPF aus? Welche Bibiliothek ist schneller?
@ALLE
Ich habe jetzt den Test nochmals auf Konsole portiert, diesmal ohne Thread für das rekursive Parsen des Dateisystems.Die Ergebnisse sind folgende:
PC 1 2 3 Datenmenge [GB] 27,7 36,2 86,3 Einheit [s] [s] [s] .NET First Run [b]98,27[/b] [b]202,70[/b] [b]548,80[/b] JAVA First Run 137,96 369,40 596,89 .NET Cached [b]4,10[/b] [b]6,58[/b] [b]25,14[/b] JAVA Cached 6,66 10,64 33,48 --------------------------------- PC1+2: Intel Core 2 CPU 6600 @ 2.40GHz, 3,25GB RAM (MEIN PC) PC3 : Pentium 4; 3,60 GHz; 2 GB RAM
Dass C# jetzt durchweg das Rennen gewinnt ist auf jeden Fall ein Indiez für eine schlechte GUI-Bibliothek, oder?
Was meint Ihr, woran die schlechte GUI-Performance festzumachen ist?
Gruß
Schang
-
Ohne die dazu passenden Sources wird Dir da keiner eine Antwort geben können.
-
es kommt auf die hardware an ob WPF schneller ist - da WPF den inhalt rendert und wenns die grafikkarte her gibt es diese machen laesst (wenns nicht sogar ohne pruefung an die graka delegiert wird, muesste ich nochmal nachlesen)
ich selber hab nur kurz mit forms rum gemacht und bin gleich zu WPF ueber
-
schang schrieb:
@lok:
ich bin in erster Linie C#-Entwickler. Deshalb würde ich für den am Anfang beschriebenen Fall dieses genau umdrehen, also: "Best Practise" für C# und "Worst Practise" für Java/SWT.Best Practice bedeutet nicht immer max Performance.
Z.B. wird folgendes durchgehend als Best Practice vorgestellt:
String ausgabe = String.Format("{0} {1}", "Hallo", "Welt");
Dies ist aber gleichzeitig auch die langsamste der möglichen Lösungen. Warum man es trotzdem als Best Practice nehmen sollte liegt darin, das reine Performance nicht immer oberstes Gebot ist.
z.B. die schnellste Lösung ist immer noch
String ausgabe = "Hallo" + "Welt";
Oder z.B. sollte man in C# alle methoden einer Klasse die nicht auf Klassenvariablen zugreifen aus Performancegründen als static markieren weil dadurch bestimmte Prüfungen zu Laufzeit wegfallen.
Erschwerend kommt hinzu das der JIT Compiler den Code ja erst auf der Zielmachine Compiliert/Optimiert und das auch von Laufzeitvariablen wie freiem Speicher etc abhängig machen kann. Das gleiche C# Programm kann also durchaus zu unterschiedlichem Binärcode mit unterschiedlichen Optimierungen führen je nach Rechner.
Wie andere schon sagten, ohne konkreten Code kann man hier nicht mehr viel mehr zu sagen.
-
z.B. die schnellste Lösung ist immer noch
C# Code:
String ausgabe = "Hallo" + "Welt";string ausgabe = "Hallo Welt";
-
Knuddlbaer schrieb:
z.B. die schnellste Lösung ist immer noch
C# Code:
String ausgabe = "Hallo" + "Welt";string ausgabe = "Hallo Welt";
Ist (in diesem Fall) identisch. Der Compiler verschmilzt Konstanten.
-
nicht ganz das selbe #gg
String ausgabe1 = "Hallo" + "Welt"; string ausgabe2 = "Hallo Welt"; ausgabe1 == "HalloWelt"; ausgabe2 == "Hallo Welt";
-
Ist das nicht eher ein reiner I/O-Performance Test? Bei deinem Test vergleichst du praktisch nur die Geschwindigkeit und den Zugriff auf die darunter liegende Festplatte und eine Menge Stack-Operationen.
Ich bezweifle auch, dass die GUI bei deiner Funktion ein Flaschenhals ist. Um das heraus zu finden, vergleiche mal die Aktivität beider GUIs.
Um die wirkliche Performance zu testen, würde ich mehr Wert auf Garbage Collector (Dynamischer Speicher), große String-Operationen, Collections etc. verpackt in Multi-Threading legen. Das kommt doch einer realen Software wesentlich näher.
Gruß Vanish
-
Hallo Vanish
Zuerst einmal existierte die Anwendung bereits, bestehend aus einem WorkerThread, der die Platte durchsucht und Ergebnisse in der GUI anzeigt. Ich habe diese "kleine" Software ausgewählt, um eine normale "Anwendung" zu testen, die ein paar Controls hat. Zugegeben, dieser Test ist sehr I/O-lastig. Doch das wußte ich vor dem Test bereits.
Die einzige GUI-Aktion, die in der Software während der Messung ständig passiert, ist das Schreiben des aktuellen Suchpfades in einer Listbox an oberster Position (Index==0), sowie das Anhängen der Ergebnisse darunter.
Meiner Meinung nach war dies eher ein Test/Vergleich über die Stärke des Frameworks, d.h. die Frage war: "Wie performant ist das .NET-Framework im Vergleich zu Java". Ich war selber überascht, dass es ausgerechnet an der GUI lag, dass meine Applikation so schlecht abschnitt. Wenn man beide Testläufe vergleicht (mit und ohne GUI) kann man dies wahrscheinlich schon rauslesen. Ohne GUI ist klar C# vorne. Mit GUI hat Java/SWT zumindest bei Single-Core die Nase vorn.
VanishOxiAction schrieb:
Um die wirkliche Performance zu testen, würde ich mehr Wert auf Garbage Collector (Dynamischer Speicher), große String-Operationen, Collections etc. verpackt in Multi-Threading legen. Das kommt doch einer realen Software wesentlich näher.
Klar, wenn ich die GUI abschalte, reduziert sich die Prüfung auf die von Dir benannten Dinge. Und das zeigt auch mein zweiter Test. Ohne GUI ist C#.NET klar schneller.
Bei einem weiteren Test, der alle Dateien als FileInfo in eine Generic-List ablegt, und anschließend durchsucht, ist C# beim durchlaufen der List um Faktor >10 schneller als Java!!!
Wenn jetzt mein Test nichts über die Performance der GUI aussagt, welche Tests müsste man machen, damit man die GUI-Performance bestimmen kann?
Ich weiß, dass die SWT Bibliothek nativ kompiliert ist und WinForms nicht. Liegt es vielleicht daran?
Gruß
schang
-
Hast Du es mal mir Profiling versucht um einfach zu schauen welcher Teil die meiste Rechenzeit verbraucht?
-
Knuddlbaer schrieb:
Ohne die dazu passenden Sources wird Dir da keiner eine Antwort geben können.
-
Die .NET GUI ist furchtbar lahm, das überrascht mich garnicht.
Um die wirkliche Performance zu testen, würde ich mehr Wert auf Garbage Collector (Dynamischer Speicher), große String-Operationen, Collections etc. verpackt in Multi-Threading legen. Das kommt doch einer realen Software wesentlich näher.
Äh. Bei Server-Anwendungen und Commandline-Tools vielleicht. Bei typsichen GUI Applikationen die kaum was rechnen aber viele bunte Fenster haben ist es ganz sicher nicht so, da überwiegt der Teil der im GUI Framework draufgeht bei weitem.
-
Huh ich bezog mich auf die allgemeine Performance der Frameworks und Trennung der Ebenen. Gut, ich habe bisher kaum Software geschrieben, aber meine Programme hatten immer ordentlich etwas zu tun, die GUI diente dem Zweck der Benutzerfreundlichkeit und eventuell für Statusmeldungen.
Ich wundere mich, wieso die GUI so ein Flaschenhals sein soll. schang lagert die Rechnung (viel I/O) sogar in einem extra Thread aus, da müsste die GUI eigentlich wenig Einfluss auf die Performance haben.
Dass bei einem komplexen Programm die GUI einen wesentlichen Anteill trägt, klar. Aber davon kann bei schangs Testprogramm doch keine Rede sein...
Gruß Vanish
-
Windows.Forms ist von Haus aus nur Single-Threaded. Wenn man also aus einem Worker-Thread heraus etwas an der Form verändern will muss man das Invoke-Pattern benutzen. Ich hab das zwar noch nie im Bezug auf Performance näher betrachted, könnte mir aber vorstellen das hier ein Bottleneck entstehen könnte.
-
BeginInvoke und EndInvoke ist eigentlich sehr performant, die Backgroundworker Komponente wrapped das auch nur. Er soll mal posten wie der Workerthread mit dem GUI Thread kommuniziert.
-
loks schrieb:
...muss man das Invoke-Pattern benutzen...
Stimmt, Invoke für synchrone Abarbeitung (langsamer) BeginInvoke für asynchrone Abarbeitung (schneller).
Ich habe jetzt mal einen Profiler drüberlaufen lassen, und folgendes Ergebnis:
Beim Dual-Core hat der Main-Thread (oder GUI-Thread) fast nichts zu tun während, der Worker sich fast überschlägt. Deswegen sind die paar Aktionen mit der GUI eine willkommene Abwechslung. (Es ist wirklich so, dass beide Threads auf unterschiedlichen Cores laufen!)Beim Single-Core ist das Ergebnis anders. Da bekommt jeder Thread seine Zeit zugewiesen, während der andere warten muss. Das Umschalten der Threads benötigt Zeit und das Warten, bis die GUI bedient wurde auch.
Bei nächsten Untersuchungen habe ich die GUI abgeändert, indem ich den Suchpfad nicht in die ListBox sondern in eine Textbox schreibe. Das Ergebnis war unglaublich. TextBox verbessert die Performance um Faktor 3 gegenüber einer ListBox (Natürlich nur auf Single-Core-PC's. Dual-Core keine Verbesserung!).
Hier die Ergebnisse (PC: Pentium M; 1,80Ghz; 1GB RAM; C-Platte: 32,7 GB):
[b]Plattform:[/b] [b]C#[/b] [b]Java[/b] First Run - TextBox: [b]184,02 s[/b] 184,47 s Cached - TextBox: [b]11,87 s[/b] 12,87 s - ListBox: 29,61 s [b]11,87 s[/b] - Label : 25,4 s [b]14,24 s[/b]
Während Java/SWT in annähernd die gleiche Zeit für alle Controls benötigt, gibt es bei .NET erhbliche Unterschiede (bis Faktor 2,5), was man auch durchaus als bottleneck bezeichnen kann. Deshalb komm ich zu dem Schluß, in .NET muss man besonders darauf achten, welche GUI-Elemente man für welche Zwecke einsetzt.
Zu guter Letzt muss ich noch etwas berichtigen. Meine Aussage in einem vorherigen Beitrag:
Bei einem weiteren Test, der alle Dateien als FileInfo in eine Generic-List ablegt, und anschließend durchsucht, ist C# beim durchlaufen der List um Faktor >10 schneller als Java!!!
stimmt der Faktor 10 nicht (Programmierfehler)!! Es sind nur 4
!!!
@Zustimmer:
Zustimmer schrieb:
Er soll mal posten wie der Workerthread mit dem GUI Thread kommuniziert.
Ich haben den BackgroundWorker nicht verwendet. Ich habe einen eigenen Thread in Verbindung mit BeginInvoke programmiert.
Beispiel:// Rekursive Methode ParseDirs(DirectoryInfo currentDir) { ... foreach (DirectoryInfo oSubDInfo in oDInfo.GetDirectories()) ParseDirs(oSubDInfo); .... AddValueToList(currentDir.FullName, 0); ... } delegate void DelAddValueToList(string zString, int iIndex); public void AddValueToList(string zString, int iIndex) { if (InvokeRequired) BeginInvoke(new DelAddValueToList(AddValueToList), new object[] { zString, iIndex }); else { if (iIndex < 0) lstResult.Items.Add(zString); else txtPath.Text = zString; // lblPath.Text = zString; // lstResult.Items[iIndex] = zString; } }