Java vs. C. Performance-Vergleich



  • Mit dem openJDK 7 komme ich auf folgende Werte:
    `real 1m54.053s

    user 1m54.060s

    sys 0m0.050s`

    Also knapp 5% besser.

    Edit: wo wir gerade dabei sind:
    r=10000(!) gcj-4.6 -O3 march=native
    `real 0m36.228s

    user 0m36.100s

    sys 0m0.090s`

    Das mit r=100000 läuft immer noch und wenn ich mal hochrechnen darf, wird's wahrscheinlich eine Stunde dauern.
    Hätte es gerne auch mit LTO ausprobiert um Inlining zu ermöglichen, aber den Versuch quittiert mir sowohl gcj-4.5 wie auch 4.6 mit unterschiedlichen ICEs.

    Jedenfalls hat da die JRE doch deutlich die Nase vorn.



  • Hätte ehrlich gesagt nicht gedacht, dass C++ Java immernoch so sehr überlegen ist. Oo Bin da ehrlich gesagt etwas enttäuscht!



  • Gregor schrieb:

    Java wird da aus folgendem Grund ein Problem bekommen: Es gibt in Java einen Overhead für jedes Objekt, der 8 Byte groß ist. Zudem steckst Du nicht die Objekte in das Array, sondern nur Zeiger auf diese Objekte. Auch nochmal 8 Byte. Deshalb sind Java-Objekte einfach viel größer als C++ Objekte. Du sprengst den Cache damit früher, brauchst mehr Speicher und so weiter.

    Nochmal zum Verständnis: Willst du damit sagen, dass in Java Objekte in Arrays so etwas wie verkettete Listen sind?!



  • Steffo schrieb:

    Gregor schrieb:

    Java wird da aus folgendem Grund ein Problem bekommen: Es gibt in Java einen Overhead für jedes Objekt, der 8 Byte groß ist. Zudem steckst Du nicht die Objekte in das Array, sondern nur Zeiger auf diese Objekte. Auch nochmal 8 Byte. Deshalb sind Java-Objekte einfach viel größer als C++ Objekte. Du sprengst den Cache damit früher, brauchst mehr Speicher und so weiter.

    Nochmal zum Verständnis: Willst du damit sagen, dass in Java Objekte in Arrays so etwas wie verkettete Listen sind?!

    Nein. ...vielleicht erläutere ich das morgen nochmal genauer.



  • Ändern sich die Java Zeiten wenn die Applikation mehrmals nacheinander gestartet wird?



  • Gregor schrieb:

    Steffo schrieb:

    Gregor schrieb:

    Java wird da aus folgendem Grund ein Problem bekommen: Es gibt in Java einen Overhead für jedes Objekt, der 8 Byte groß ist. Zudem steckst Du nicht die Objekte in das Array, sondern nur Zeiger auf diese Objekte. Auch nochmal 8 Byte. Deshalb sind Java-Objekte einfach viel größer als C++ Objekte. Du sprengst den Cache damit früher, brauchst mehr Speicher und so weiter.

    Nochmal zum Verständnis: Willst du damit sagen, dass in Java Objekte in Arrays so etwas wie verkettete Listen sind?!

    Nein. ...vielleicht erläutere ich das morgen nochmal genauer.

    Ok, wenn Du in Java ein Array von Point Objekten mit jeweils 2 int Variablen (2 * 4 Byte) hast, dann sieht das IMMER so aus:

    ---------   ---------   ---------
      |Point 1|   |Point 2|   |Point 3|
      | 8+4+4 |   | 8+4+4 |   | 8+4+4 |     (3 Point
      | Byte  |   | Byte  |   | Byte  |      Objekte)
      ---------   ---------   ---------
          ^           ^           ^
          |           |           |         (3 Zeiger)
    ------|-----------|-----------|------
    |     |     |     |     |     |     |
    |     |     |     |     |     |     |   (Array mit
    |     X     |     X     |     X     |    3 Elementen)
    |  8 Byte   |  8 Byte   |  8 Byte   |
    |           |           |           |
    -------------------------------------
    

    In jedem Array-Platz ist nur ein Zeiger auf das jeweilige Objekt gespeichert. Ich gehe von einer 64-Bit JVM aus, da sind diese Zeiger dann natürlich 8 Byte groß. Zudem hat jedes Objekt einen Overhead von 8 Byte, in dem praktisch abgespeichert ist, zu welcher Klasse dieses Objekt gehört. Das macht insgesamt 8+8+4+4=24 Byte Speicherbedarf pro gespeichertem Point Objekt.

    In C++ kannst Du hingegen auch so etwas haben:

    -------------------------------------
    | --------- | --------- | --------- |
    | |Point 1| | |Point 2| | |Point 3| |
    | |  4+4  | | |  4+4  | | |  4+4  | |
    | |  Byte | | |  Byte | | |  Byte | |
    | --------- | --------- | --------- |
    -------------------------------------
    

    Hier kannst Du die Objekte also direkt ins Array stecken und hast nicht notwendigerweise eine Indirektion durch irgendwelche Zeiger, die viel Platz benötigen. Zudem hast Du hier keinen Overhead, in dem der Typ des Objekts steht. Zusammengefasst hast Du hier also nur 4+4=8Byte Speicherbedarf pro gespeichertem Point Objekt.

    Natürlich macht sich das vor allem bei Massen an sehr kleinen Objekten bemerkbar. Aber dann kann es eben durchaus sein, dass ein Javaprogramm ein Vielfaches des Speichers benötigt, den ein entsprechendes C++Programm benötigt.

    War das verständlich?



  • Die Erklärung ist verständlich, danke Gregor. Aber weshalb werden in Java auf diese Weise Objekte mit so viel Overhead implementiert? Hat das unter anderem etwas mit Reflections zu tun?

    L. G.
    Steffo



  • Steffo schrieb:

    Aber weshalb werden in Java auf diese Weise Objekte mit so viel Overhead implementiert?

    Weil Java keine benutzerdefinierten Typen mit Wertsemantik kennt. Imo eine der ganz ganz großen Schwächen von Java.



  • dot schrieb:

    Steffo schrieb:

    Aber weshalb werden in Java auf diese Weise Objekte mit so viel Overhead implementiert?

    Weil Java keine benutzerdefinierten Typen mit Wertsemantik kennt. Imo eine der ganz ganz großen Schwächen von Java.

    Wenn man eine Wertzuweisung möchte, dann kann man ja die Clone-Methode benutzen. Referenzzuweisung ist ja eigentlich etwas, was Speicherplatz spart und billig ist.

    L. G.
    Steffo



  • Der Punkt ist aber, dass du Objekte (von int, float etc. mal abgesehen) in Java ausschließlich nur über Referenzen ansprechen kannst. Es gibt keinen Weg Objekte direkt anzufassen. Darum kannst du auch nur Referenzen auf Objekte in Container packen und nicht die Objekte selbst, was die Implementierung von "Generics" in Java natürlich trivial macht. Referenzzuweisungen sind vielleicht billig. Aber welcher Code besteht schon einzig und allein aus Zuweisungen und der Zugriff auf ein Objekt über eine Referenz ist dagegen nicht gratis. Abgesehen davon sind Referenzen und Werte einfach rein semantisch völlig was anderes. Das eine ist kein Ersatz für das andere...



  • Ich hab mich mal mit dem Thema neu beschäftigt und war ziemlich erstaunt:

    File Copy einer Datei mit einer Größe von 733.6 MB:

    Java:

    real 0m1.415s
    user 0m0.124s
    sys 0m1.278s

    C:

    real 0m1.707s
    user 0m0.000s
    sys 0m1.685s

    Offenbar ist Java hier in zwei von drei Bereichen schneller.

    Hier der Code (der nicht auf Sicherheit getrimmt ist):

    import java.io.*;
    import java.nio.channels.FileChannel;
    
    public class FileChannelCopy {
    
        public static void main(String[] args) throws IOException {
            // Pfade entsprechend anpassen
            File inF = new File(args[0]);
            File outF = new File(args[1]);
            copyFile(inF, outF);
        }
    
        public static void copyFile(File in, File out) throws IOException {
            FileChannel inChannel = new FileInputStream(in).getChannel();
            FileChannel outChannel = new FileOutputStream(out).getChannel();
            try {
                inChannel.transferTo(0, inChannel.size(), outChannel);
            } catch (IOException e) {
                throw e;
            } finally {
                if (inChannel != null)
                    inChannel.close();
                if (outChannel != null)
                    outChannel.close();
            }
        }
    }
    
    #include <stdio.h>  /* fprintf */
    #include <string.h> /* strerror */
    #include <stdlib.h>
    
    #include <fcntl.h>    /* open, O_RDONLY, O_WRONLY, O_CREAT, O_EXCL */
    #include <sys/stat.h> /* mode_t, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH */
    #include <unistd.h>   /* read, write */
    #include <errno.h>    /* errno */
    
    int main(int argc, char *argv[])
    {
      const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; /* rw-r--r-- */
      int in, out; /* Dateideskriptoren */
      unsigned char *b;
      int size;
      struct stat buf;
    
      if (argc != 3)
      {
          fprintf(stderr, "Aufruf: %s Quelle Ziel\n", argv[0]);
          return 1;
      }
    
      in = open(argv[1], O_RDONLY);
      if (in == -1)
      {
          fprintf(stderr,
    	      "Quelle %s kann nicht geoeffnet werden (errno %d: %s)\n",
    	      argv[1], errno, strerror(errno));
          return 1;
      }
    
      if (fstat(in, &buf) == -1)
      {
        fprintf(stderr,
    	      "Es konnten keine Informationen aus Quelle '%s' ausgelesen werden (errno %d: %s)\n",
    	      argv[1], errno, strerror(errno));
        return 1;
      }
    
      size = (int) buf.st_size;
      printf("Größe: %d\n", size);
      b = (unsigned char*) malloc(sizeof(unsigned char) * size);
    
      out = open(argv[2], O_WRONLY | O_CREAT | O_EXCL, mode);
      if (out == -1)
      {
          fprintf(stderr,
    	      "Ziel %s kann nicht erzeugt werden (errno %d: %s)\n",
    	      argv[2], errno, strerror(errno));
          return 1;
      }
    
      if (read(in, b, size) == -1)
      {
        fprintf(stderr,
    	    "Quelle %s kann nicht gelesen werden (errno %d: %s)\n",
    	    argv[1], errno, strerror(errno));
          return 1;
      }
    
      if (write(out, b, size) == -1)
      {
          fprintf(stderr,
    	      "Schreibfehler (errno %d: %s)\n", errno, strerror(errno));
          return 1;
      }
    
      close(out);
      close(in);
    
      free(b);
    
      return 0;
    }
    

    Die Datei soll in einem Stück ausgelesen und geschrieben werden.

    Verwendet wurden OpenJDK 6 und GCC 4.6.2 mit den Optionen -W -Wall -pedantic -O3 -march=core2.

    Vielleicht kennt jemand eine perfomantere C-Implementierung?

    Ich habe hier mal POSIX-Aufrufe genommen, weil ich dachte, dass es nichts schnelleres als Systemaufrufe gibt.

    L. G.
    Steffo



  • Warum der Code sowieso suboptimal ist wurde dir ja schon im java forum gesagt, ansonsten ist ein Test, der nur eine große Datei kopiert höchst ungeeignet um 2 Sprachen miteinander zu vergleichen. Das einzige was du da misst ist die Geschwindigkeit deiner Festplatte/des Treibers.



  • Steffo schrieb:

    Vielleicht kennt jemand eine perfomantere C-Implementierung?

    http://linux.die.net/man/2/sendfile

    nur so nebenbei, ich habe noch keine app gesehen und ich rede hier nicht von ein paar zeilen code, wo java c outperformed hätte 🙄



  • gassssssst schrieb:

    Warum der Code sowieso suboptimal ist wurde dir ja schon im java forum gesagt, ansonsten ist ein Test, der nur eine große Datei kopiert höchst ungeeignet um 2 Sprachen miteinander zu vergleichen. Das einzige was du da misst ist die Geschwindigkeit deiner Festplatte/des Treibers.

    Die Vorgehensweise ist doch bei beiden gleich: Erst die Datei komplett in einen Buffer lesen und dann auf einen Schlag schreiben. Das Festplattenargument versteh ich nicht ganz, schließlich laufen beide Programme auf demselben System und es gibt einen Geschwindigkeitsunterschied.
    Mit der gleichen Logik kann man sagen, dass Benchmarks immer nur die Prozessorgeschwindigkeit messen...



  • das os cached von der platte gelesene daten in ram. 🙄



  • KingKarl schrieb:

    Steffo schrieb:

    Vielleicht kennt jemand eine perfomantere C-Implementierung?

    http://linux.die.net/man/2/sendfile

    Werde ich später mal ausprobieren, danke.



  • Steffo schrieb:

    Die Vorgehensweise ist doch bei beiden gleich: Erst die Datei komplett in einen Buffer lesen und dann auf einen Schlag schreiben.

    wo hast das gelesen? kennst du die implementation von obj.transferTo?



  • Probier mal http://msdn.microsoft.com/en-us/library/windows/desktop/aa363851(v=vs.85).aspx

    Und wenn du die Geschwinigkeiten irgendwie ernsthaft vergleichen willst, dann nimm nur Lese oder Schreibgeschwindigkeit. Aber auch das dürfte nicht sonderlich spannend sein. Das ist einfach nur ein Aufruf, und der limitierende Faktor ganz klar die Festplatte*. Streng genommen kann man schon sehen, dass irgendwo ein Fehler sein muss, sobald man stark abweichende Ergebnisse bekommt.

    Wenn du die Geschwindigkeiten vergleichen willst, dann nimm irgendetwas mit vielen Rechenoperationen und Speicherzugriffen. Parsing, Lineare Algebra, ...

    Edit:
    * Und nein, das ist nicht das gleiche Argument wie "sonst misst man ja nur die CPU". Es geht ja eben darum zu schauen, mit welcher Sprache man die CPU optimaler ansprechen kann. Bei dem Festplattenzeugs hier wird aber ziemlich simpel ein Befehl ans System gesendet und das wars dann. Da haben die Sprachen nicht viel mit zu tun.



  • KingKarl schrieb:

    Steffo schrieb:

    Die Vorgehensweise ist doch bei beiden gleich: Erst die Datei komplett in einen Buffer lesen und dann auf einen Schlag schreiben.

    wo hast das gelesen? kennst du die implementation von obj.transferTo?

    schau mal was in der doku steht...

    FileChannel.transferTo...

    This method is potentially much more efficient than a simple loop that reads from this channel and writes to the target channel. Many operating systems can transfer bytes directly from the filesystem cache to the target channel without actually copying them.

    http://docs.oracle.com/javase/1.4.2/docs/api/java/nio/channels/FileChannel.html#transferTo%28long,%20long,%20java.nio.channels.WritableByteChannel%29



  • Damit wurde bewiesen, dass mit java sehr wohl I/O-Benchmarks geschrieben werden können, wenn die Dateien gross genug sind. Benchmarking ist nicht so einfach. Schau mal hier:

    $ time java Copy copy_c foo

    real 0m0.061s
    user 0m0.045s
    sys 0m0.015s

    $ time ./copy_c copy_c foo
    Größe: 8695

    real 0m0.002s
    user 0m0.002s
    sys 0m0.000s

    Damit kann ich beweisen, dass das C-Programm 2 Millisekunden braucht, das Java-Programm aber 61. Also ist C um den Faktor 30,5 schneller - oder? Oder beweist das, dass ein Java Programm einfach ca. 30 mal so lange braucht, bis es los läuft? Oder beweist das eher, dass der Vergleich Java vs. C mit einem I/O-Benchmark nicht wirklich entschieden werden kann?


Anmelden zum Antworten