Performace-Test: Java vs. C#



  • 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.

    @VanishOxiAction:

    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;
        }
    }
    


  • 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 ^^


Anmelden zum Antworten