Performance C++ vs. Java



  • @wob sagte:

    (du testest doch mit angeschalteten Optimierungen?)

    Tatsächlich muss ich zu meiner Schande gestehen: Nein, bis dahin leider nicht. Vielen Dank für den Hinweis 🙂

    Aber ich habe noch etwas weiter geforscht und optimiert. Nachdem ich die Synchronisation der C++ Streams und den C Streams abgeschaltet habe, rennt das C++ Programm:

    #include <iostream>
    #include <chrono>
    
    int main() {
        auto begin = std::chrono::high_resolution_clock::now();
    
        std::ios::sync_with_stdio(false);
    
        for (long long i = 0; i < 10000000; i++) {
            std::cout << i << "\n";
        }
    
        auto end = std::chrono::high_resolution_clock::now();
    
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-begin);
    
        std::cout << elapsed.count() << std::endl;
    }
    

    Jetzt braucht Java 169.156 Millisekunden und C++ nur noch 45.924 Millisekunden.

    Man sollte jetzt natürlich nicht mehr sowas machen:

    #include <iostream>
    #include <cstdio>
     
    int main()
    {
        std::ios::sync_with_stdio(false);
        std::cout << "a\n";
        std::printf("b\n");
        std::cout << "c\n";
    }
    

    Aber das mache ich ja auch nicht.



  • Dieser Performance-Vergleich macht so oder so keinen Sinn.
    Das ist so trivial, da traue ich Java auch durchaus zu, das sinnvoll zu optimieren.

    Es gibt andere Faktoren, die eine Rolle spielen. z.B., dass du in C++ bei komplexeren Problemstellungen einfach Eingriffs- und Optimierungsmöglichkeiten hast, die du in Java eben nicht hast.
    Oder auch einfach solche Sachen wie das Objektmodell von Java (oder die Generics). Das erzeugt teilweise extremen Overhead, um den man entweder gar nicht, oder nur umständlich rumkommt.



  • @Timmi87 sagte in Performance C++ vs. Java:

    Jetzt braucht Java 169.156 Millisekunden und C++ nur noch 45.924 Millisekunden.

    Du misst immer noch Mist, bzw. vergleichst Du welche Runtime schneller etwas in die Konsole klatschen kann. Das interessiert keine $tiername.



  • @Swordfish sagte in Performance C++ vs. Java:

    Das interessiert keine $tiername.

    😁 😁 😁



  • @Timmi87
    Du testest so halt auch die Performance der Konsole mit. Auf Windows würde ich schätzen dass das viel länger als 45ms braucht. Leite die Ausgabe bei der Ausfürhung in ein File um. Idealerweise eins in einer Ram-Disk/temp-fs.

    Oder besser: mach was was nicht so viel Ausgaben benötigt.
    Gut für Benchmarks ist einen Wert zu berechnen und diesen dann auszugeben.
    Die Berechnung darf aber nicht trivial sein, sonst wird sie komplett wegoptimiert. Also z.B. die Summe aller Zahlen von 1 bis X ist zu einfach, das optimiert dir der Compiler weg.



  • Da ich letztlich Code für eine simple Matritzenmulitplikation brauchte, habe ich diesen Code geschrieben. Ich hoffe, ich habe die korrekte Indexberechnung genommen, und nicht die falsche Order. Der Code der Matrix Klasse ersetzt eine deutlich komplexere Klasse, die noch nicht fürs Veröffentlichen geeignet ist.

    #include <iostream>
    #include <cstdlib>
    #include <vector>
    #include <chrono>
    #include <ratio>
    #include <cmath>
    #include <memory>
    
    //#include "matrix.h"
    
    using namespace std::chrono;
    //using namespace std;
    
    template<typename T>
    class Matrix {
    	size_t m_, n_;
    	std::unique_ptr<T[]> p_;
    public:
    	Matrix (const size_t m, const size_t n) : m_(m), n_(n), p_(new T[m*n]) {}
    	T& operator() (const size_t m, const size_t n) noexcept {
    		return p_[m+m_*n];
    	}
    };
    
    int main () {
    	std::vector<std::pair<size_t, size_t>> matrix_dim_rep =
    	{{16, 1000}, {24, 1000}, {32,1000}, {48, 1000}, {64, 1000}, {75,1000}, {96, 1000}, {100,1000}, {128, 1000},
    	 {150, 1000}, {200, 500}, {250, 100}, {256, 100}, {260, 100}, {275, 100}, {295,100}, {350, 100}, {400,100},
    	 {450, 50}, {512, 100}, {525, 50}, {550, 50}, {592, 10}, {605, 10}, {620, 10}, {750, 10}, {1024, 10}};
    
    	std::cout << "'size','time(ms)'\n";
    
    	for (auto mdp: matrix_dim_rep) {
    		auto s = mdp.first;
    		Matrix<double> A (s, s), B (s, s), C (s,s);
    
    		for (size_t i = 0; i != s; ++i) {
    			for (size_t j = 0; j != s; ++j) {
    				A(i,j) = rand() % 100 + 1.0;
    				B(i,j) = rand() % 100 + 1.0;
    				C(i,j) = rand() % 100 + 1.0;
    			}
    		}
    
    
    		steady_clock::time_point t1 = steady_clock::now();
    		for (size_t rep = 0; rep != mdp.second; ++rep) {
    			for (size_t i = 0; i != s; ++i) {
    				for (size_t j = 0; j != s; ++j) {
    					for (size_t k = 0; k != s; ++k) {
    						C(i,j) = A(i, k) * B(k, j);
    					}
    				}
    			}
    		}
    		steady_clock::time_point t2 = steady_clock::now();
    		duration<double,std::milli> d = t2 - t1;
    		d /= mdp.second;
    		double w = 100*pow(d.count(),1.0/3.0)/double(s);
    
    		std::cout << s << "," << d.count() << "," << w << "\n";
    	}
    
        return EXIT_SUCCESS;
    }
    


  • Performancevergleiche sind eine hohe Kunst mit einer hohen Fehlerquote. D.h. man tut gut daran erstmal jede Menge Erfahrung in beiden Sprachen zu sammeln bevor man sich an sowas herantraut. Du siehst ja, dass selbst bei so einer trivialen Schleife viele Fehler gemacht werden können ( Consolenoutput messen???). Zudem vergleichst du hier nur einen kleinen Teilaspekt der beiden Sprachen. Bei C++ gibt es außerdem noch 100 verschiedene Compiler, dazu 100 Compileflags. Geht dir ein Lichte auf, junger Padawan? 🙂



  • @It0101 sagte in Performance C++ vs. Java:

    Performancevergleiche sind eine hohe Kunst mit einer hohen Fehlerquote. D.h. man tut gut daran erstmal jede Menge Erfahrung in beiden Sprachen zu sammeln bevor man sich an sowas herantraut.

    Naja, das bezieht sich auf Micro-Benchmarks.

    Wenn ich 2 Programme habe und das eine 5 Minuten, das andere aber 10 Minuten braucht, dann brauche ich da keine schwarze Magie für für den Vergleich heranzuziehen. Ob es dann allerdings an der Programmiersprache liegt oder am Programmierer (bzw. am verwendeten Algorithmus), ist eine andere Frage.

    Daher ist aus "Programm P1 in Programmiersprache A braucht doppelt so lange wie Programm P2 in Programmiersprache B" noch lange nicht abzuleiten, dass die Sprache A langsamer ist als B. Vielleicht wurde einfach das falsche Konstrukt verwendet, was für diejenige Sprache nicht idiomatisch ist.



  • @wob Ich widerspreche dir nicht. Ich will nur darauf hinweisen dass man eine Menge Background-Wissen haben muss, um die Messergebnisse dann auch einordnen zu können.

    Wenn man z.B. einen vector-artigen Container vergleicht kann allein durch den Allokator im Hintergrund ein großer Unterschied auftreten. Wenn bei 10000 Elementen der eine vector alle 10 Elemente jeweils 10 neue allokiert und der andere direkt 10000 Elemente auf einen Schlag, dann ist allein dadurch ein deutlich spürbarer Unterschied gegeben.



  • @It0101 sagte in Performance C++ vs. Java:

    Wenn bei 10000 Elementen der eine vector alle 10 Elemente jeweils 10 neue allokiert und der andere direkt 10000 Elemente auf einen Schlag, dann ist allein dadurch ein deutlich spürbarer Unterschied gegeben.

    Das ist sogar nicht nur eine Unterschied zwischen Sprachen, sondern auch zwischen C++-Compilern. Mircosoft hat nämlich genau das früher so gemacht.



  • @Jockelx sagte in Performance C++ vs. Java:

    @It0101 sagte in Performance C++ vs. Java:

    Wenn bei 10000 Elementen der eine vector alle 10 Elemente jeweils 10 neue allokiert und der andere direkt 10000 Elemente auf einen Schlag, dann ist allein dadurch ein deutlich spürbarer Unterschied gegeben.

    Das ist sogar nicht nur eine Unterschied zwischen Sprachen, sondern auch zwischen C++-Compilern. Mircosoft hat nämlich genau das früher so gemacht.

    Das halte ich für ein Gerücht.

    Also wenn du mit "früher" vor > 25 Jahren oder so meinst, vielleicht. Aber bei VC++ 6 (=vor 23 Jahren) war es auf jeden Fall schon OK.



  • @hustbaer sagte in Performance C++ vs. Java:

    Das halte ich für ein Gerücht.

    Möglich, zumindest finde ich da auch nichts zu. Meine Firma hat mir mal ein Online-Seminar von Rainer Grimm spendiert und da hat er das erzählt. Ist keine schlechte Quelle und daher hab ich das ungeprüft mal so weiter gegeben.



  • @wob sagte in Performance C++ vs. Java:

    Vielleicht wurde einfach das falsche Konstrukt verwendet

    Über den folgenden Code bin ich vor kurzem gestolpert.

    string s = ""; // ODS XML
    
    s += "<document>";
    // ... 60 MByte Aktiendaten
    s += "</document>";
    

    Bis ich den C# StringBuilder nutzte.

    Der Code ist dummerweise C++ und C# kompatibel. Dahinter stehen aber unterschiedliche Konzepte der mutable, immutable Strings.



  • @Quiche-Lorraine Du kannst auch string.reserve aufrufen. Aber es ist natürlich nachfragenswert, warum man sowas in einem String haben will...



  • @It0101 sagte in Performance C++ vs. Java:

    Wenn man z.B. einen vector-artigen Container vergleicht kann allein durch den Allokator im Hintergrund ein großer Unterschied auftreten. Wenn bei 10000 Elementen der eine vector alle 10 Elemente jeweils 10 neue allokiert und der andere direkt 10000 Elemente auf einen Schlag, dann ist allein dadurch ein deutlich spürbarer Unterschied gegeben.

    Es geht noch viel subtiler. Man muss nur Speicher irgend wie allozieren und dann z.B. SIMD-Code darauf loslassen, und dann stimmt die Performance nicht, weil man das unpassende Alignment genutzt hat.



  • @wob sagte in Performance C++ vs. Java:

    Aber es ist natürlich nachfragenswert, warum man sowas in einem String haben will...

    Der Code war ein Schnellschuss für eine Ausgabe nach LibreOffice ODS. Und ich meinte, ich habe zuerst den Dateiinhalt bestimmt, diesen dann nach UTF8 gewandelt und dann erst in die XML Datei geschrieben.

    Aber der Knackpunkt war die langsame XML Ausgabe in C#. Die Ursache lag an der Nutzung von string in Kombination mit dem += Operator. Da unter C# immutable sind, wird pro += Aufruf ein neuer String erzeugt.

    Und da lag mein Denkfehler. Unter C++ ist ein string ein Container, unter C# ein unveränderlicher String.

    Aber wie schon gesagt, unter C# löste StringBuilder mein Problem. Unter C++ hatte ich das Problem noch nie.


  • Gesperrt

    @Timmi87 : Schreib einen vernünftigen Test (um die CPU/ eine Schleife zu testen, und nicht das Speichermedium...), und vergleiche dann die Ausgaben:

    #include <limits.h>
    #include <math.h>
    #include <iostream>
    #include <chrono>
    
    bool is_p(long long int test)
    {
        if (test % 2 == 0) {
            return false;
        }
        long long int s = sqrt(test);
        for (long long int i = 3; i <= s; i += 2) {
            if (test % i == 0) {
                return false;
            }
        }
        return true;
    }
    
    int main(int argc, char* argv[])
    {
        using namespace std;
        chrono::time_point<chrono::high_resolution_clock> t1 = chrono::high_resolution_clock::now();
        for (long long int i = LLONG_MAX;; i--) {
            bool p = is_p(i);
            cout << i << " : " << p << endl;
            if (p) {
                break;
            }
        }
        chrono::time_point<chrono::high_resolution_clock> t2 = chrono::high_resolution_clock::now();
        cout << chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << endl;
        return 0;
    }
    

    Das sollte keins der Sprachen optimieren können. Ausgabe bei mir:

    a.exe
    9223372036854775807 : 0
    9223372036854775806 : 0
    9223372036854775805 : 0
    9223372036854775804 : 0
    9223372036854775803 : 0
    9223372036854775802 : 0
    9223372036854775801 : 0
    9223372036854775800 : 0
    9223372036854775799 : 0
    9223372036854775798 : 0
    9223372036854775797 : 0
    9223372036854775796 : 0
    9223372036854775795 : 0
    9223372036854775794 : 0
    9223372036854775793 : 0
    9223372036854775792 : 0
    9223372036854775791 : 0
    9223372036854775790 : 0
    9223372036854775789 : 0
    9223372036854775788 : 0
    9223372036854775787 : 0
    9223372036854775786 : 0
    9223372036854775785 : 0
    9223372036854775784 : 0
    9223372036854775783 : 1
    17067
    

    Schaffst du es, das Java-Äquivalent zu formulieren?


    Unterm Strich werden aber beide in etwa "gleichschnell" sein.



  • @EinNutzer0 sagte in Performance C++ vs. Java:

    bool is_p(long long int test)
    

    Nenn die Funktion doch ruhig is_prime und spendiere ihr noch den Check auf den Spezialfall test == 2, damit sie auch richtig wird 🙂

    Signifikant schneller wirds z.B. so:

    bool is_prime(long long int test)
    {
        if (test % 2 == 0 || test % 3 == 0) {
            return test == 2;
        }
        long long int s = sqrt(test);
        for (long long int i = 5; i <= s; i += 4) {
            if (test % i == 0) {
                return false;
            }
            i += 2;
            if (test % i == 0) {
                return false;
            }
        }
        return true;
    }
    

    Und dann kann man mal anfangen, die Schleife zu optimieren. Vielleicht stören ja die beiden if-Bedingungen? Also:

        for (unsigned long long int i = 5; i <= s; i += 6) {
            if ((test % i == 0) + (test % (i + 2) == 0) != 0) return false;
        }
    

    Siehe da, das ist bei mir schneller (in Kombi mit s und i als unsigned).

    Was lernen wir: Erst den Algorithmus optimieren!



  • @wob sagte in Performance C++ vs. Java:

    bool is_prime(long long int test)
    {
        if (test % 2 == 0 || test % 3 == 0) {
            return test == 2;
        }
    

    Bei test == 3 kommt da aber nicht das raus was ich mir erwarten würde...



  • @Jockelx sagte in Performance C++ vs. Java:

    @hustbaer sagte in Performance C++ vs. Java:

    Das halte ich für ein Gerücht.

    Möglich, zumindest finde ich da auch nichts zu. Meine Firma hat mir mal ein Online-Seminar von Rainer Grimm spendiert und da hat er das erzählt. Ist keine schlechte Quelle und daher hab ich das ungeprüft mal so weiter gegeben.

    Spätestens jetzt wäre interessant was er da genau gesagt hat.
    vector::push_back ist bei MSVC auf jeden Fall OK und war schon sehr lange OK.
    vector::insert (speziell range-insert) bin ich nicht ganz so sicher.

    Beim Erstellen eines vector aus dem Inhalt zweier Iteratoren bin ich auch nicht 100% sicher dass hier nicht unnötig re-allocated wurde. Ich bin sicher dass nicht linear re-allocated wurde, aber es wäre möglich dass da unnötigerweise exponentiell re-allocated wurde.


Anmelden zum Antworten