ubuntuusers.de

[Java] Ist Java langsamer als C/C++?

Status: Gelöst | Ubuntu-Version: Kein Ubuntu
Antworten |

oldy22

Avatar von oldy22

Anmeldungsdatum:
9. August 2016

Beiträge: 67

Wohnort: Tübingen

Ging mir auch grad so, dass ein recht einfaches Java Programm 10x schneller war als die vergleichbaren C, C++ Versionen (mit gcc kompiliert) ... daher hab ich auch mal geschaut und finde das hier recht einleuchtend:

https://www.computerweekly.com/de/feature/Java-versus-C-Welche-Programmiersprache-ist-schneller

Bearbeitet von rklm:

Link korrigiert

Moderiert von ChickenLipsRfun2eat:

Der Beitrag ist von topic/java-ist-java-wirklich-so-viel-lngsamer-als-c abgetrennt worden. Bitte lasse die Toten ruhen! (Verhaltenscodex)

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

oldy22 schrieb:

Ging mir auch grad so, dass ein recht einfaches Java Programm 10x schneller war als die vergleichbaren C, C++ Versionen (mit gcc kompiliert)

"Und wenn es nicht gestorben ist, dann läuft es noch heute..."

Mal im Ernst: Da wurde entweder falsch gemessen oder sehr schlechtes C geschrieben. Ein 10-fach schnelleres Java klingt jedenfalls nicht sehr realistisch.

oldy22

(Themenstarter)
Avatar von oldy22

Anmeldungsdatum:
9. August 2016

Beiträge: 67

Wohnort: Tübingen

hier mal die Quellen https://github.com/oldy-22/test/tree/main/Codetastatur

gcc 90sec. ca java 16sec. als nur 6x langsamer ☺

aber trotzdem für mich entgegen meiner landläufige (bisher ungeprüften) Meinung...

vg daniel

apt-ghetto

Anmeldungsdatum:
3. Juni 2014

Beiträge: 2943

Solange man nicht weiss, mit welchen Optionen und Optimierungen die Programme kompiliert wurden, kann man auch keine sinnvolle Aussage dazu treffen.

Im Prinzip müsstest du den Maschinencode von beiden Varianten kennen, um zu sehen, wo die Unterschiede sind. Dann kann man auch eine Aussage dazu treffen, welche Variante schneller ist. Und anschliessend könnte man die langsamere Variante mit dem gewonnenen Wissen wahrscheinlich auch optimieren.

ChickenLipsRfun2eat Team-Icon

Anmeldungsdatum:
6. Dezember 2009

Beiträge: 12067

Wie genau hast du kompiliert? Läuft bei Java auch alles auf einem Kern? Habe das C-Dingen mal für nen Spaß mit musl, clang und gcc laufen lassen, da kamen Zeiten von knapp >1m (clang) bis 4m (musl ohne alles) bei raus.

Java hab ich nicht.

oldy22

(Themenstarter)
Avatar von oldy22

Anmeldungsdatum:
9. August 2016

Beiträge: 67

Wohnort: Tübingen

Eigentlich kann es nur auf 1 core laufen, da die Aufgaben nicht entkoppelbar sind (Schritt 2 bedingt Schritt 1...). Hab es mit gcc in eclipse übersetzt (Arch x86_64 und Optimierung most -O3 (nicht auf Größe) eingestellt) und es braucht mit den libc Funktionen um die 90sec. bei mir und in JAVA 16!!!

🤓

Hätte eher das Gegenteil erwartet...

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17618

Wohnort: Berlin

Wenn der Artikel typisch ist für Computer Weekly, dann würde ich von der Publikation abraten.

Java ist eine interpretierte Sprache. Im Gegensatz dazu ist C++ – eine Sprache, mit der Java oft verglichen wird – statisch typisiert. Es ist die dynamische Natur der Java-Sprache, die den Benutzern oft Sorgen über mögliche Geschwindigkeitsprobleme bereitet.

Ob man Java eine interpretierte Sprache nennt ist strittig, das stellt der Artikel aber nicht falsch dar, denn es wird ja dann erklärt, dass ein Zwischencode (Javaslang: Bytecode) per Compiler erzeugt wird, der dann auf der JVM läuft und da es JVMs für verschiedene Betriebssysteme und Rechnerarchitekturen/CPUs gibt, kann jede ihre eigenen Optimierungen vornehmen, ohne dass beim Kompilieren eine Optimierungsentscheidung getroffen werden muss.

Was aber falsch ist, dass ist die Behauptung, dass die statische Typisierung von C++ ein Unterschied zu Java wäre und damit zu tun hätte. Die vage Formulierung "dynamische Natur der Javasprache" ist auch fishy; Java wird in der Regel nicht zu den dynamischen Programmiersprachen gezählt, weil sie eben statisch typisiert ist.

Statisch typisiert heißt, dass man für die Variablen (Objekte, Member, Parameter von Methoden, Rückgabewerte von Mehoden, ...) vorher festlegen muss (deklarieren), welchen Typs sie sind (int, boolean, String, Integer, JFrame, ResultSet, BufferedOutputStream, ...).

Das ist die große Gemeinsamkeit von C/C++/Java.

Wenn ein Java-Programm ausgeführt wird, untersucht es das zugrunde liegende System, und die JVM kann den von ihr erstellten Maschinencode auf der Grundlage der verfügbaren Funktionen optimieren.

Nein, das macht nicht das Javaprogramm. Sonst müsste ja jeder Programmierer, der Javaprogramme schreibt, davon wissen und wissen wie es geht. Das macht die JVM.

Was richtig ist, ist, dass Javaprogramme in Microbenchmarks oft Kopf an Kopf mit C oder C++-Programmen sind, dass aber immer ein Crack in der Sprache X mit der Optimierung Y ums Eck kommen kann, und die andere Sprache liegt wieder vorn.

Idiomatischer Javacode, der nach Möglichkeit Wort für Wort nach C++ übersetzt wird, das funktioniert oft, weil die Syntax der Sprachen so ähnlich ist und weil der Code so ähnlich aussieht, sieht es dann wie ein fairer Vergleich aus. Und Java ist dann wahrscheinlich schneller, aber idiomatischer C++-Code, den man wortweise nach Java übersetzt, ist dann wahrscheinlich schneller als der Javacode.

Mikrobenchmarks wie hier werden gerne und oft angestellt, aber haben nur eine begrenzte Aussagekraft. Für verschiedene Operationen haben verschiedene CPUs eingebaute Optimierungen oder auch nicht, ein Crack würde den Code oft anders schreiben, insbes. wenn es wichtig wäre das letzte aus einem Programm herauszukitzeln - evtl. würde ein C- oder C++-Programmierer sogar zu Assembler greifen. Optimierende Compiler oder ein optimierendes JRE können auch den Code analysieren und reinen Rechenaufwand, dessen Ergebnis am Ende ignoriert wird, komplett wegoptimieren, so dass man aufpassen muss, wie man seinen Mikrobenchmark implementiert.

Eine sehr große Rolle spielt das Problem des Cachings, d.h. ob alle Werte und Zwischenwerte in den Cache passen (L1, L2, ...). So kann ein Programm A bei 100.000 Elementen vorne sein, Programm B bei 1.000.000 Elementen und bei 10.000.000 vielleicht wieder A.

Die JVM optimiert Code, abhängig davon, wie oft der läuft, d.h. wenn man einen Schwellwert übersteigt kann es plötzlich um eine Größenordnung schneller werden.

Wenn Du einen Server der im Netz hängt, und 10 Mio. User täglich mit einem Wordlerätsel beschäftigt optimierst, dann wirst Du wohl genau für den Rechner, die mittlere Userzahl, Anzahl Cores und Größe des RAMs optimieren (oder einen doppelt so schnellen Server kaufen - das ist oft billiger). Wenn Du Anwendercode schreibst wie eine Officesuite, die auf 1000 unterschiedlichen Systemen performant laufen soll sind die Optimierungsprobleme ganz andere.

Allgemein sind die interpretierten Sprachen, die nicht in Zwischencode compiliert werden, wie Ruby, Python, PHP usw. langsamer als compilierte Sprachen wie C/C++/Java, aber für viele gibt es Bindungs an die JVM, und man kann den Code auch in Bytecode für die JVM übersetzen lassen (keinerlei Erfahrungen aus der Praxis meinerseits) und wahrscheinlich für manche Sprachen auch Tools, um sie in C-Code u.ä. zu transformieren.

Für Java sieht es vergleichweise schlecht aus, wenn es um sehr kleine Scripts geht. Beim ersten Start muss erst die JVM in den Speicher geladen werden - da ist das C-Programm längst fertig. Beim zweiten Lauf hat das OS aber vielleicht noch im Speicher gelassen und startet deutlich schneller.

Benchmarking ist eine komplizierte Sache bei der viel zu beachten ist.

Als langjähriger Javaprogrammierer, der vorher aber C und C++ benutzt hat und nur deswegen bei Java gelandet ist, weil es sich ganz ordentlich geschlagen hat, sind meine Äußerungen auch nicht frei von Bias. Dass Java 8x oder 10x schneller sein soll - bei diesem speziellen Test aber nur - das klingt verdächtig. Meist hat man Unterschiede von 80%-120% oder nur von 95%-105% in der Laufzeit.

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11260

Wohnort: München

Ich habe das Java-Programm mal in einer Ubuntu 20.04 VM auf einem alten Core i5-2500 laufen lassen:

$ javac ZahlenMitStrings.java
$ time java ZahlenMitStrings
5611032

real	2m25,315s
user	2m24,483s
sys	0m0,378s 

Die Variante für C und C++ lagen mit den Compile-Flags -O3 -march=native für gcc bzw. g++ bei über 8 Minuten.

Jetzt ist die Frage, was da bei den Varianten für C und C++ so langsam ist - ich würde auf sprintf und std::to_string tippen: https://www.zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html. Also mal versuchsweise eine Umstellung auf fmtlib/fmt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <fmt/compile.h>
#include <string>
#include <iostream>
using namespace std;

int main() {
		int counter = 0; // zaehlt Zahlen ohne Wiederholungen von Ziffern in der Zahl
		bool checker; // nur wenn true gab es keine Wiederholungen

		for (int i=1000; i<999999999; ++i) {
			checker = true;
			//const string zahl = to_string(i);
                        auto f = fmt::format_int(i);
                        auto zahl = f.c_str();
                        int zahl_len = f.size();

			for (int j=0; j<zahl_len; j++) {
				if (checker == false) break;
				for (int k=1+j; k<zahl_len; k++) {
					if (zahl[j] == zahl[k]) {
						checker=false; break;
					}
				}
			}
			if ( checker == true) counter++;
	}
	cout << counter;
	return 0;
}
$ g++ test_cpp.cpp -O3 -march=native -lfmt -o fmt_cpp
$ time ./fmt_cpp 
5611032
real	1m10,422s
user	1m10,387s
sys	0m0,000s 

Und schon ist C++ doppelt so flott wie Java.

Dann habe ich das C++-Programm aufgeräumt, so dass man ohne die Konvertierung der Zahlen in einen String auskommt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

int main() {
    int counter = 0; // zaehlt Zahlen ohne Wiederholungen von Ziffern in der Zahl

    for (int i = 1000; i < 999999999; ++i) {
        int digit_counter[10] = {};
        int zahl=i;
        ++counter;
        while (zahl > 0) {
            int digit = zahl % 10;
            zahl /=10;
            if (digit_counter[digit] < 1) {
                ++digit_counter[digit];
            } else {
                --counter;
                break;
            }
        }
    }
    std::cout << counter << '\n';
    return 0;
}

Das sieht schon deutlich besser aus:

$ g++ -O3 -march=native cpp_optimized.cpp 
$ time ./a.out 
5611032

real	0m32,592s
user	0m32,547s
sys	0m0,020s 

Das selbe mit Java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ZahlenMitStringsOptimized {
	public static void main(String[] args) {
		int counter = 0; // zahlt Zahlen ohne Wiederholungen von Ziffern in der Zahl
		
		for (int i=1000; i<999999999; ++i) {
			int[] digit_counter = new int[10];
                        counter++;
			
			int number = i;
			while(number > 0) {
				int digit = number % 10;
				number /= 10;
				if (digit_counter[digit] < 1) {
					digit_counter[digit]++;
				} else {
					counter--; // Zähler dekrementieren, wenn es eine Wiederholung gibt
					break;
				}
			}
		}
		System.out.println(counter);
	}
}

ist etwas langsamer als C++ aber immer noch deutlich schneller als die ursprüngliche Variante:

$ javac ZahlenMitStringsOptimized.java
$ time java ZahlenMitStringsOptimized 
5611032

real	0m47,409s
user	0m46,824s
sys	0m0,242s 

Also können wir uns vielleicht darauf einigen, dass sprintf und std::to_string suboptimal sind, um "nur" Integer in Strings umzuwandeln und der ursprünliche Algorithmus in keiner Sprache optimal war, um das Problem flott zu lösen...

oldy22

(Themenstarter)
Avatar von oldy22

Anmeldungsdatum:
9. August 2016

Beiträge: 67

Wohnort: Tübingen

Schau mal - bei mir läuft es ganz anders (auch i5)? Also die String Variante. Komisch -gell???

1
2
3
4
5
6
time java ZahlenMitStrings 
5611032

real	0m24,966s
user	0m25,282s
sys	0m0,118s

Warum ist das so unterschiedlich wie bei dir? Du hast recht, die libc Funktionen sind langsam - hab hier einen besser mit mir stimmenden Vergleich bekommen und ein super Programm noch mit dazu:

https://www.wer-weiss-was.de/t/ist-java-viel-schneller-als-c-in-manchen-dingen/9500841/2

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11260

Wohnort: München

oldy22 schrieb:

Schau mal - bei mir läuft es ganz anders (auch i5)? Also die String Variante. Komisch -gell???

1
2
3
4
5
6
time java ZahlenMitStrings 
5611032

real	0m24,966s
user	0m25,282s
sys	0m0,118s

Warum ist das so unterschiedlich wie bei dir?

Wie oben geschrieben war das in einer VirtualBox VM auf einem Rechner mit einem 11 Jahre alten Core i5-2500 (der nebenbei noch ein paar andere Dinge zu tun hat) mit openjdk 14.0.2 2020-07-14. Bei den aktuellen Core i5 ist die Single-Thread Performance ca. drei mal höher.

Du hast recht, die libc Funktionen sind langsam - hab hier einen besser mit mir stimmenden Vergleich bekommen und ein super Programm noch mit dazu:

https://www.wer-weiss-was.de/t/ist-java-viel-schneller-als-c-in-manchen-dingen/9500841/2

Sieht nach einer ähnlichen Idee in C aus, nur ist da die Prüfung auf Wiederholungen in eine extra Funktion ausgelagert, der Code ist etwas kompakter geschrieben und es wird erst nach dem Schleifendurchlauf geprüft, ob er abbrechen soll (was Sinn macht, weil die Funktion nie mit 0 als Argument aufgerufen wird).

Neral

Anmeldungsdatum:
3. Oktober 2007

Beiträge: 230

user_unknown schrieb:

Allgemein sind die interpretierten Sprachen, die nicht in Zwischencode compiliert werden, wie Ruby, Python, PHP usw. langsamer als compilierte Sprachen wie C/C++/Java

Alle der genannten dynamisch typisierten Sprachen werden (genau wie Java) von allen gängigen Implementierungen in einen Zwischencode (== Bytecode) kompiliert. Und alle drei haben Implementierungen, die (wie gängige Java-Implementierungen) einen JIT benutzen.

Dass Python, Ruby und PHP langsamer als C++ oder Java sind, liegt nicht daran, dass sie nicht kompiliert werden (weil sie in allen gängigen Implementierungen kompiliert werden), sondern daran, dass sie dynamisch typisiert sind und deshalb Optimierungen im JIT sehr viel schwieriger werden als bei statisch typisierten Sprachen.

Es macht auch keinen Sinn, von interpretierten oder kompilierten Sprachen zu reden. Das ist eine Eigenschaft der Implementierung. Man wird für jede Sprache (ja, auch C++) Implementierungen finden, die Code interpretieren, die Code kompilieren und die beides machen.

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17618

Wohnort: Berlin

Neral schrieb:

Es macht auch keinen Sinn, von interpretierten oder kompilierten Sprachen zu reden. Das ist eine Eigenschaft der Implementierung. Man wird für jede Sprache (ja, auch C++) Implementierungen finden, die Code interpretieren, die Code kompilieren und die beides machen.

Dem stimme ich zu, aber es es gar nicht so leicht einen C-Interpreter zu finden; für C++ wohl erst recht, mag aber sein, dass ich da nicht ganz up to date bin. Gibt es interaktive Shells oder REPLs für C und C++?

Dass Ruby, Python und PHP i.d.R. nicht interpretiert werden, sondern auch kompiliert werden war mir dagegen neu. Ich dachte das sei die Ausnahme.

Kompiliert vs. interpretiert ist sicher kein hartes Unterscheidungskriterium, aber unter Umständen findet man in Praxis für eine Sprache vor allem das eine oder das andere vor, und für manche Sprachen hält es sich womöglich auch die Waage.

Scala kommt zum Beispiel mit einer REPL und die JShell ist wohl was ähnliches. Bei der JShell weiß ich es nicht, aber bei Scala wird, auch i.d. REPL, der Code immer kompiliert, so dass es aussieht, als werkle da ein Interpreter.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13202

user_unknown schrieb:

Wenn der Artikel typisch ist für Computer Weekly, dann würde ich von der Publikation abraten.

Ja, ich fand den auch etwas unterkomplex. Die Schlussfolgerung am Ende, dass es vom Anwendungsfall abhängt, was schneller ist, stimmt aber. Allerdings ist heutzutage die Ausführungsgeschwindigkeit zunehmend weniger wichtig als die Entwicklerproduktivität (es gibt natürlich Ausnahmen).

Was aber falsch ist, dass ist die Behauptung, dass die statische Typisierung von C++ ein Unterschied zu Java wäre und damit zu tun hätte. Die vage Formulierung "dynamische Natur der Javasprache" ist auch fishy; Java wird in der Regel nicht zu den dynamischen Programmiersprachen gezählt, weil sie eben statisch typisiert ist.

Ich denke, gemeint ist nicht die Typisierung sondern die Bindung der Methoden. In C++ hat man beides und in Java ist es immer dynamisch. Aber der Artikel ist da sehr schwammig.

Allgemein sind die interpretierten Sprachen, die nicht in Zwischencode compiliert werden, wie Ruby, Python, PHP usw.

Mindestens Python nutzt Bytecode. Bei Ruby hängt es davon ab, welchen Interpreter man nimmt.

Benchmarking ist eine komplizierte Sache bei der viel zu beachten ist.

!

Neral

Anmeldungsdatum:
3. Oktober 2007

Beiträge: 230

Jeder C++-Compiler hat einen C++-Interpreter eingebaut, weil die Sprache C++ erfordert, dass (quasi) beliebiges C++ zur Compilezeit ausgeführt können werden muss (→ constexpr und consteval). Und eine interaktive Shell gibt es in Form von cling. Circle ist eine Übermenge von C++, die beliebigen C++-Code (auch I/O) in einem Interpreter ausführen kann.

Für Rust gibt es den Interpreter miri, dessen Hauptanwendungszweck das dynamische Überprüfen der safety von unsafe-Code ist.

Das hier ist eine C++-Implementierung, die zur Laufzeit C++-Code aus Strings nach Maschinencode kompiliert (man würde so ein Feature eigentlich nur in einer „Interpretersprache“ wie Python mit eval erwarten).

Python wird in allen großen Implementierungen – wie Java – vor der Ausführung in Bytecode kompiliert. Siehe die .pyc-Dateien im __pycache__-Verzeichnis. Ruby macht das genau so, nur dass der Bytecode nicht gespeichert wird.

Für beides gibt es JITs, die den Bytecode zur Laufzeit nochmal in Maschinencode kompilieren. PyPy für Python und YJIT für Ruby zum Beispiel.

Für Python gibt es mit nuitka und mypyc auch „klassische“ Ahead-of-Time-Compiler. Insbesondere mypyc ist sehr weit verbreitet.

user_unknown schrieb:

Bei der JShell weiß ich es nicht, aber bei Scala wird, auch i.d. REPL, der Code immer kompiliert, so dass es aussieht, als werkle da ein Interpreter.

Das macht die Python-REPL in der CPython-Implementierung genau so: Read Compile Eval Print Loop

Antworten |