Performace-Test: Java vs. C#
-
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; } }
-
Du hast ein implizites Boxing in Deinem Code. Abhängig davon wie of das aufgerufen wird kann das schon etwas Performance kosten.
delegate void DelAddValueToList(string zString, int iIndex); public void AddValueToList(string zString, int iIndex) { if (InvokeRequired) BeginInvoke(new DelAddValueToList(AddValueToList), new object[] { zString, iIndex });
iIndex wird hier implizit geboxed. MSDN sagt dazu:
http://msdn.microsoft.com/en-us/library/ms998574.aspx#scalenetchapt13_topic10
[msdn]
Boxing causes a heap allocation and a memory copy operation. Review your code to identify areas where implicit boxing occurs. Pay particular attention to code inside loops where the boxing overhead quickly adds up. Avoid passing value types in method parameters that expect a reference type. Sometimes this is unavoidable. In this case, to reduce the boxing overhead, box your variable once and keep an object reference to the boxed copy as long as needed, and then unbox it when you need a value type again.
[/msdn]Auch zum Thema Recursion steht da was:
[msdn]
Do You Use Recursion?
If your code uses recursion, consider using a loop instead. A loop is preferable in some scenarios because each recursive call builds a new stack frame for the call. This results in consumption of memory, which can be expensive depending upon the number of recursions. A loop does not require any stack frame creation unless there is a method call inside the loop.If you do use recursion, check that your code establishes a maximum number of times it can recurse, and ensure there is always a way out of the recursion and that there is no danger of running out of stack space.
[/msdn]
Beides (Recursion und Boxing) stressed das Memory management.
-
Hackt nicht soviel auf der armen Rekursion rum.
Der JITter ist inzwischen ganz gut darin, Tail Calls zu produzieren. Endrekursive Funktionen haben daher (in der Regel) kein Overhead.
-
VanishOxiAction schrieb:
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
Die .NET GUI ist z.B. schonmal deswegen langsam weil an vielen Stellen GDI+ verwendet wird, und GDI+ ist leider nicht hardware-beschleunigt (CPU rendert alles). Das ist IMO ein ganz grosser Punkt.
Dann kommt noch dazu dass das ganze .NET Framework im allgemeinen, und die System.Windows.Forms.* Dinge im speziellen, eher auf Mächtigkeit, Einfachkeit, Korrektheit, Sicherheit Wert legen als auf Speed, und dementsprechend programmiert sind. Was auch durchaus OK ist - schnellere PCs kosten weniger als höhere Entwicklungszeit. Auch Checks wie die dass eine GUI Komponente nur von "richtigen" Thread verwendet wird kosten Performance. "IsDisposed" Checks kosten Performance. Fast alles was einem das Leben einfacher macht kostet Performance, und das .NET Framework hat was GUI angeht schon etliche Dinge die einem das Leben einfacher machen.
Dann verwenden viele managed GUIs die OS GUI Komponenten, so auch das .NET Framework. Da entsteht einiges an Aufwand durch das andauerndte hin-und-her marschallen von Daten, die andauernden Wechsel von managed zu unmanaged. Der Übergang managed-unmanaged muss auch für sehr sehr viele Messages durchgeführt werden wo im managed Teil dann eh bloss "nix" gemacht wird, weil in der entsprechenden Handler-Funktion eben nur "mach default" steht. Dann hast du für viele Messages eine Member-Funktion und zusätzlich noch einen Event der da drauf lauscht. Die Member-Funktion muss ausgeführt werden, der Event muss gecheckt werden (selbst wenn er null ist)... viel viel Overhead einfach.
Und dann kommt halt noch dazu dass man gewisse Dinge halt heute einfach von einer Applikation erwartet, und da diese im .NET Framework recht einfach zu machen sind baut sie jeder ein. Automatisches re-flowen bzw. allgemein Layout-Anpassung beim Ändern der Grösse eines Fensters sind z.B. Sachen mit denen man immens viel Rechenpower verbraten kann.
-
hustbaer schrieb:
Die .NET GUI ist z.B. schonmal deswegen langsam weil an vielen Stellen GDI+ verwendet wird, und GDI+ ist leider nicht hardware-beschleunigt (CPU rendert alles). Das ist IMO ein ganz grosser Punkt.
Vielleicht kann schang ja mal die CPU-/Systemauslastung während des Testens beobachten oder mitloggen ....
-
loks schrieb:
O
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?
Was ist denn nun besser (performanter). Und warum?? Würde mich gerne etwas weiterbilden.
-
hustbaer schrieb:
...
Die .NET GUI ist z.B. schonmal deswegen langsam weil an vielen Stellen GDI+ verwendet wird, und GDI+ ist leider nicht hardware-beschleunigt (CPU rendert alles). Das ist IMO ein ganz grosser Punkt.Dann kommt noch dazu dass das ganze .NET Framework im allgemeinen, und die System.Windows.Forms.* Dinge im speziellen, eher auf Mächtigkeit, Einfachkeit, Korrektheit, Sicherheit Wert legen als auf Speed, und dementsprechend programmiert sind...
Hm, und wo sollte man diese niedrigere Performance zu spüren bekommen? Ich hab noch nie erlebt das ein Windows-Form irgendwo hakt weil da zu viel gezeichnet würde oder zu viele Controls in der Maske sind. Wer natürlich mit dem GDI Spiele programmiert ist wohl selber schuld ^^
-
Cpp_Junky schrieb:
hustbaer schrieb:
...
Die .NET GUI ist z.B. schonmal deswegen langsam weil an vielen Stellen GDI+ verwendet wird, und GDI+ ist leider nicht hardware-beschleunigt (CPU rendert alles). Das ist IMO ein ganz grosser Punkt.Dann kommt noch dazu dass das ganze .NET Framework im allgemeinen, und die System.Windows.Forms.* Dinge im speziellen, eher auf Mächtigkeit, Einfachkeit, Korrektheit, Sicherheit Wert legen als auf Speed, und dementsprechend programmiert sind...
Hm, und wo sollte man diese niedrigere Performance zu spüren bekommen? Ich hab noch nie erlebt das ein Windows-Form irgendwo hakt weil da zu viel gezeichnet würde oder zu viele Controls in der Maske sind. Wer natürlich mit dem GDI Spiele programmiert ist wohl selber schuld ^^
Mach ein DataGrid (docked), und lade da 200 Zeilen rein.
Dann ändere die Fenster-Grösse, so dass sich die Grösse des Grids mit ändert.
Da kannst du zusehen, wie die Daten von oben nach unten neu aufgebaut werden. Dauert locker ein paar hundert ms. Auf einem 2.13 Ghz Core 2 Duo mit GeForce 6600.
Dasselbe mit "native" Controls geht so schnell, dass es nichtmal gescheit flimmert, geschweide denn dass man sehen könnte in welcher Richtung er die Grafik aufbaut.Wer das nicht langsam nennt, hat wohl gänzlich andere Vorstellungen von schnell und langsam als ich...
p.S.: versuch mal ein selbst gezeichnetes Control zu machen, und mach da einfach nur ein FillRect über die ganze Fläche. Ersetze das DataGrin im Beispiel oben durch das selbst gezeichnete Control, und lass das Programm laufen. *schnurch*
Und wenn du noch Transparenz mit dazunimmst ist alles endgültig aus.
-
@schang: Es ist witzlos, die Performance von zwei Sprachen zu vergleichen, ohne die jeweiligen Implementierungen zu zeigen. Das macht alle Aussagen, die Du in dem Bereich machst, unseriös und unglaubwürdig.
-
THE_ONE schrieb:
Was ist denn nun besser (performanter). Und warum??
Erster Denkfehler besser und performanter ist nicht das Gleiche. Was nutzt Performance wenn es nur bedeutet das das Programm schneller abstürzt?
THE_ONE schrieb:
Würde mich gerne etwas weiterbilden.
weiterbilden ist ein aktiver Vorgang. Wenn Du Dich wirklich weiterbilden willst, MSDN is your friend...
http://msdn.microsoft.com/de-de/library/2839d5h5(VS.80).aspx
-
[quote="loks"]
THE_ONE schrieb:
Erster Denkfehler besser und performanter ist nicht das Gleiche. Was nutzt Performance wenn es nur bedeutet das das Programm schneller abstürzt?
http://msdn.microsoft.com/de-de/library/2839d5h5(VS.80).aspx
Erstmal danke für dein Feedback!
In diesem Fall ist die StringBuilder Variante der String Variante vorzuziehen wenn es um die Performance geht, korrekt?
Was ist aber unsicherer? Meiner Meinung nach sind beide gleich sicher.
Bei String wird immer wieder eine neuer String erzeugt.
Bei StringBuilder wird "Bei Bedarf automatisch Speicherplatz zugeordnet"Alles in allem gesehen ist daher die StringBuilder Variante auch besser, korrekt?
-
hustbaer schrieb:
Mach ein DataGrid (docked), und lade da 200 Zeilen rein.
Dann ändere die Fenster-Grösse, so dass sich die Grösse des Grids mit ändert.
Da kannst du zusehen, wie die Daten von oben nach unten neu aufgebaut werden.Das liegt wohl eher daran das da sowas wie Autoresize angeschaltet ist und der sich neu zurechtformatiert. Ich will mal behaupten das die olsche Grid-Komponente im VS6 den selben Eiertanz vorführen würde, sofern es solche Autosize Funktionen überhaupt hätte ^^
p.S.: versuch mal ein selbst gezeichnetes Control zu machen, und mach da einfach nur ein FillRect über die ganze Fläche. Ersetze das DataGrin im Beispiel oben durch das selbst gezeichnete Control, und lass das Programm laufen. *schnurch* ...
Ich hab ne ganze Palette eigener Controls mit komplett eigenen Darstellungsfunktionen. Da kommt nicht nur FillRect sondern FillRect mit Gradient, Antialias-Text und Bildchen drin und da flimmert nix, geschweige denn das irgendein Performance-Einbruch spürbar wäre. Vielleicht ist deine Vorgehensweise nicht so optimal, oder mein Rechner ist einfach zu schnell
(P4 @ 3Ghz)
-
@Gregor n wenk spät, nich ?
-
Cpp_Junky schrieb:
hustbaer schrieb:
Mach ein DataGrid (docked), und lade da 200 Zeilen rein.
Dann ändere die Fenster-Grösse, so dass sich die Grösse des Grids mit ändert.
Da kannst du zusehen, wie die Daten von oben nach unten neu aufgebaut werden.Das liegt wohl eher daran das da sowas wie Autoresize angeschaltet ist und der sich neu zurechtformatiert. Ich will mal behaupten das die olsche Grid-Komponente im VS6 den selben Eiertanz vorführen würde, sofern es solche Autosize Funktionen überhaupt hätte ^^
Nein, das hat mit Autosize nix zu tun. Autosize ist auch nicht aktiv, das Control ist einfach in ein Panel reingedockt. Das "sizing" der einzelnen Panels/Zellen ist dabei auch nicht das Problem, sondern das "malen" der Zellen sowie das "malen" der Selektierung. Man muss dazu auch garnix resizen, es reicht wenn man "links oben" draufklickt um alle Zeilen zu markieren, und dann in eine Zelle um alle Zeilen wieder zu deselektieren.
Wenn das Fenster full-screen ist (=1280x1024, ca. 40 Zeilen, je 14 Spalten, also "nichts"), dauert das Selektieren/Deselektieren bei mir je ca. 0,5 Sekunden.
Als Release compiliert, und NICHT aus dem Studio raus gestartet (beides macht nen wilden Unterschied). Allerdings auf meinem Entwicklungs-PC gestartet -- weiss nicht ob das .NET Framework irgendwie "umkonfiguriert" wird, wenn man Visual Studio installiert.
Studio ist 2005, OS ist XP Pro SP3.
Ich kann mal ein Beispiel-Programm machen, wenn du mir nicht glaubstp.S.: versuch mal ein selbst gezeichnetes Control zu machen, und mach da einfach nur ein FillRect über die ganze Fläche. Ersetze das DataGrin im Beispiel oben durch das selbst gezeichnete Control, und lass das Programm laufen. *schnurch* ...
Ich hab ne ganze Palette eigener Controls mit komplett eigenen Darstellungsfunktionen. Da kommt nicht nur FillRect sondern FillRect mit Gradient, Antialias-Text und Bildchen drin und da flimmert nix, geschweige denn das irgendein Performance-Einbruch spürbar wäre. Vielleicht ist deine Vorgehensweise nicht so optimal, oder mein Rechner ist einfach zu schnell
(P4 @ 3Ghz)
Same here. Keine besondere "Vorgehensweise", einfach Zeichen-Code im "OnPaint", nix aufregendes. Und da es wie gesagt Framework Controls auch betrifft, gehe ich mal davon aus dass das "normal" ist.
p.S.: SQL Server Management Studio (für SQL Server 2005), "OPEN TABLE" -> genauso langsam *schnurch*. Hat also *definitiv* nix mit meinem Code zu tun.
p.p.S.: hast du vielleicht ein anderes OS als ich (ich wie gesagt XP Pro SP3)? Das wäre vielleicht noch eine Erklärung... Oder verwendest du evtl. ne andere (neuere) Framework/Studio Version (ich 2.0/2005 SP1).
-
Denn es geht mal wieder um Java vs. C#/.NET!
Endlich mal Abwechslung ... wir haben hier meist nur C++ vs. alle anderen.
Au ja, toller Test:
Das Tool parst die C-Platte rekursiv durch, auf der Suche nach 3 vorher spezifizierten Dateien, und vergleicht die MD5-Checksumme, File-Version, und Erstellungsdatum der Dateien mit Referenzdaten.
Also testest du in Wirklichkeit einfach nur I/O anstatt die "Performance". Vielleicht solltest du vorher festlegen, welche Performance du testet, unter welchen Kriterien, Gesichtspunkten, ....
-
THE_ONE schrieb:
Alles in allem gesehen ist daher die StringBuilder Variante auch besser, korrekt?
Jein. Solche Aussagen kann man nicht pauschalisieren weil es immer auf den Context ankommt. Um zwei Strings zusammenzufügen ist der Stringbuilder die falsche Wahl, um 20 Strings zusammenzufügen dagegen die richtige Wahl.
Empfohlen dagegen wird in der MSDN String.Format() zu benutzen, was zwar nicht die schnellste Variante ist, aber die Sicherste.
Also sowas wie: String.Format("{0}{1}", string1, string2);
-
hustbaer schrieb:
...
p.p.S.: hast du vielleicht ein anderes OS als ich (ich wie gesagt XP Pro SP3)? Das wäre vielleicht noch eine Erklärung... Oder verwendest du evtl. ne andere (neuere) Framework/Studio Version (ich 2.0/2005 SP1).Jo, ich hab hier ebenfalls WinXP mit SP3, allerdings entwickel ich mit dem VS 2008 und .NET 3.5 Ich werde das mit dem Grid hier mal ausprobieren. Das man da nicht unendlich Daten reinpumpen kann war mir schon klar, aber das der bei 40 Zeilen schon rumeiert
Ich werde hier mal eine special investigation starten
EDIT
So, hab den Test gerade durchgeführt:
Gedocktes DataGridView mit DataTable als Datasource. 10.000 Zeilen je 10 Spalten mit Daten. Die einzig spürbare Verzögerung entstand beim Fetching aus der Datenbank (Firebird 2 auf selber Maschine) mit ca 0,5 Sekunden. Jedoch keine Nebeneffekte beim Ändern von Fenster/Spaltengrösse, Maximieren oder was auch immer. Selbst mit Column-Autosize hakt kaum etwas - Erst, wenn ich per Zellengrösse anpassen lasse. Was aber auch verständlich ist, bei 10.000 x 10 Zellen ^^ Version 3.5 scheint da GUI-technisch tatsächlich etwas effizienter zu arbeiten.