Performance C++ vs. Java



  • Hallo zusammen,

    mich hat mal der Vergleich zwischen C++ und Java interessiert.
    Dazu hab ich einfach je eine For-Schleife geschrieben und den Schleifenzähler ausgegeben:

    #include <iostream>
    
    int main() {
        for (int i = 0; i < 10000000; i++) {
            std::cout << i << "\n";
        }
    }
    
    public class test
    {
     
           public static void main (String[] args)
           {
                 for (int i = 0; i < 10000000; i++) {
                       System.out.println(i);
                 }
           }
    }
    

    Wenn ich beide Programme laufen lasse, dann stelle ich fest, dass Java erheblich schneller ist. Aber ich hatte es eigentlich anders erwartet. Wieso ist das so?



  • std::cout ist typischerweise line-buffered wenn es an einer Konsole hängt. D.h. es wird jede Zeile sofort ausgegeben.
    Probier mal beide Programme auszuführen und den Output in ein File umzuleiten. Dann sollte der Vorteil von Java verschwinden.



  • Ohne es zu wissen, aber ich würde auf die Ausgabe in der Konsole tippen. Versuche mal, ohne Ausgabe zu messen.



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

    Hallo zusammen,

    mich hat mal der Vergleich zwischen C++ und Java interessiert.
    Dazu hab ich einfach je eine For-Schleife geschrieben und den Schleifenzähler ausgegeben:

    Wenn ich beide Programme laufen lasse, dann stelle ich fest, dass Java erheblich schneller ist. Aber ich hatte es eigentlich anders erwartet. Wieso ist das so?

    Weil <iostream> dafür bekannt ist, extrem langsam zu sein 🙂 - hängt alles von locales und von den Modifiern aus iomanip etc. ab. Weil "Java ist langsam" ein Mythos ist (es kann Probleme in Java z.B. geben, wenn der GC zu ungünstigen Zeitpunkten anspringt, aber generell ist Java nicht langsam).

    Versuchs mal mit fmt::print("{}\n", i); aus https://fmt.dev/latest/index.html



  • @hustbaer Aber ich nutze nicht std::endl sondern "\n". Sollte dann nicht auch über die Zeile hinaus gepuffert werden? Denn mit /n wird der buffer doch nicht geflusht oder?



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

    Aber ich nutze nicht std::endl sondern "\n".

    Wenn du auf die Konsole ausgibst, wird in der Regel doch geflusht (könnte mir vorstellen, dass das bei Java auch so ist). Denn Ausgabe auf dem Bildschirm will man ja normalerweise sofort sehen. Leite doch mal in eine Datei um.



  • Ich habe die Ausgabe ansich mal in beiden Programmen gestrichen und lasse nur noch die Schleifen durchlaufen. Dabei messe ich die Zeit:

    #include <iostream>
    #include <chrono>
    
    int main() {
        auto begin = std::chrono::high_resolution_clock::now();
    
        for (int i = 0; i < 10000000; i++) {
            // doing nothing
        }
    
        auto end = std::chrono::high_resolution_clock::now();
    
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-begin);
    
        std::cout << elapsed.count();
    }
    
    public class test
    {
     
           public static void main (String[] args)
           {
                final long begin = System.currentTimeMillis();
    
                 for (int i = 0; i < 10000000; i++) {
                       // doing nothing
                 }
    
                 final long end = System.currentTimeMillis();
    
                 System.out.println(end-begin);
           }
    }
    

    Das Java-Programm gibt 1 Millisekunde aus; das C++ Programm gibt 12 Millisekunden aus.



  • Eine Schleife, die nichts tut, ist überhaupt nicht mehr im kompilierten Programm vorhanden - die wird einfach wegoptimiert. So ergibt Testen keinen Sinn. (du testest doch mit angeschalteten Optimierungen?) Bei mir kommt da 0 raus und das sollte bei dir auch rauskommen.



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


Log in to reply