Autor Beitrag
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Do 23.04.15 15:47 
Hey Leute,

ich habe gestern Nacht mal so aus Spaß einen Performancevergleich zwischen C++, C# und Java versucht. Weit bin ich nicht gekommen :mrgreen:, ich habe nur die grundlegenden Rechenoperationen (+-*/) mit Int32 Variablen durchgeführt.

Aufgebaut ist der Test so, dass ich 1Mrd. Durchläufe pro Operation mache, also 1Mrd. Additionen, 1Mrd. Subtraktionen, ... (exklusive der Zählervariable für die Iterationen)

Es gibt 3 Variablen a, b, c, wobei a und b am Anfang Random sind zwischen -32768 und +32768 und c=0 ist.
In jeder Schleife werden dann die Operationen in Folgender Reihenfolge durchgeführt:

c = a + b;
a = c - b;
c = a * b;
b = c / a;

Das Resultat (was nicht stimmen kann - da bin ich mir ziemlich sicher :mrgreen:):

C++ brauchte 13.715s für 1Mrd. Läufe
C# bracuhte 13.730s für 1Mrd. Läufe
Java brauchte 7.395s für 1Mrd. Läufe (WTF?!?!)


Die Tests liefen auf Win8.1 mit einen i7-4770k @ 3.5GHz

Warum zur Hölle ist Java so schnell??? Bzw. was ist an meinem Test falsch :D?

Hier die Codes dazu...

C++:
ausblenden volle Höhe C#-Quelltext
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:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
#include <iostream>
#include <ctime>

using namespace std;

double getDuration(clock_t start, clock_t end)
{
  return double(end - start) / CLOCKS_PER_SEC;
}

void compareInt32(uint64_t count)
{
  cout << "Measuring Int32 operations..." << endl;

  // generate numbers between -32768 and 32768
  int32_t a = rand() % 65536 - 32768, b = rand() % 65536 - 32768, c;

  clock_t start = clock();
  for (uint64_t i = count; i > 0; i--)
  {
    c = a + b;
    a = c - b;
    c = a * b;
    b = c / a;
  }
  clock_t end = clock();

  cout << "Measured " << count << " operations of adding, subtracting, multiplying and dividing: " << getDuration(start, end) << "s" << endl;
  printf("Average: %.0f operations of adding, subtracting, multiplying and dividing per second\n\n", count / getDuration(start, end));
}



void main()
{
  srand(1865486153);

  compareInt32(1000000000);
  compareInt32(1000000000);
  compareInt32(1000000000);
  compareInt32(1000000000);
  compareInt32(1000000000);

  getchar();
}


C#:
ausblenden volle Höhe C#-Quelltext
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:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
using System;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static readonly Random rdm = new Random();

        static void Main(string[] args)
        {
            CompareInt32(1000000000);
            CompareInt32(1000000000);
            CompareInt32(1000000000);
            CompareInt32(1000000000);
            CompareInt32(1000000000);

            Console.ReadKey();
        }

        static void CompareInt32(long count)
        {
            Console.WriteLine("Measuring Int32 operations...");

            Stopwatch watch = new Stopwatch();
            int a = rdm.Next(short.MinValue, short.MaxValue), b = rdm.Next(short.MinValue, short.MaxValue), c = 0;

            watch.Start();
            for (long i = count; i > 0; i--)
            {
                c = a + b;
                a = c - b;
                c = a * b;
                b = c / a;
            }
            watch.Stop();

            Console.WriteLine("Measured \{count} operations of adding, subtracting, multiplying and dividing: \{ watch.Elapsed.TotalSeconds : F3}s");
            Console.WriteLine("Average: \{count / watch.Elapsed.TotalSeconds : F0} operations of adding, subtracting, multiplying and dividing per second\n\n");
        }
    }
}


Java (die StopWatch-Klasse gibt es hier)
ausblenden volle Höhe C#-Quelltext
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:
30:
31:
32:
33:
34:
35:
36:
import java.util.Random;
import org.apache.commons.lang3.time.StopWatch;

public class Main {

    static Random rdm = new Random();
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        
        CompareInt32(1000000000);
        CompareInt32(1000000000);
        CompareInt32(1000000000);
       
    }
    
    static void CompareInt32(long count)
    {
        int a = rdm.nextInt(Short.MAX_VALUE * 2) - Short.MAX_VALUE;
        int b = rdm.nextInt(Short.MAX_VALUE * 2) - Short.MAX_VALUE;
        int c = 0;
        
        StopWatch watch = new StopWatch();
        watch.start();
        for (long i = count; i> 0; i--) {
            c = a + b;
            a = c - b;
            c = a * b;
            b = c / a;
        }
        watch.stop();
        
        System.out.println("Time: " + watch.getNanoTime() / 1000000000f);
    }

}

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Do 23.04.15 16:16 
Ich würde mal behaupten, dass du dort nur den Optimizer getestet hast: die Schleife lässt sich komplett rausoptimieren. Wenn der Compiler merkt, dass die Variablen nie benutzt werden, kann man die ganze Schleife arithmetisch (also ohne auf Overflows zu achten) auf c = a * b kollabieren. Ich könnte mir schon vorstellen, dass die Hotspot VM sowas zumindest teilweise merkt.

Auf einer etwas langsameren (3GHz) CPU übrigens 10.2 Sekunden mit FPC 3.1.1, der hat allerdings keine dieser Optimierungen gemacht.

Hast du einen Clang da?

_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Do 23.04.15 16:43 
Jap da hat die JVM wohl optimiert. Ich habe die Optimierung ausgeschaltet mit dem Build- und JVM-Arg:
ausblenden Quelltext
1:
-Djava.compiler=NONE					


Jetzt braucht Java 20.440s :D. Das klingt schon mehr nach Java.

// EDIT
Weitere Tests folgen in den kommenden Tagen - denke ich :D

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
GuaAck
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 378
Erhaltene Danke: 32

Windows 8.1
Delphi 10.4 Comm. Edition
BeitragVerfasst: Do 23.04.15 23:44 
1. Versuch (alles Delphi 7 auf einem AMD 8350 4 Ghz)

a, b, c und i waren lokale Variablen in einer Procedure; wohl vergleichbar mit den C-Code.
>>> 8,13 s; (Optimierungseinstellunge und auch Overfloweinstellungen bei Delphi sind ohen Einfluss.)

2. Versuch
Eine Class erstellt, darin a, b, c deklariert und den Algorithmus in einer Procedure der Klasse implementiert.
>>> 10.75 s;

3. Versuch
geplant: Für Versuch 1 habe ich mir mal den Assemblercode angesehen. Da gibt es m. E. noch ein paar MOV-Befehle zu viel. Versuche ich am Wochenende mal, Assembler braucht Ruhe.
>>> 7,5 s scheinen mir ereichbar zu sein.

Das zeigt, welch großen Einfluss der Zugriff hat. Die Mathematik ist sehr schnell, aber es macht einen Unterschied, ob eine Variable als lokale Variable in einer Procedure in einem Register behalten werden darf, oder ob sie jederzeit als sichtbare Klassenvariable aktuell sein sollte.

Zu der JAVA-Zeit:
Es ist nicht ausgeschlossen, dass ein Optimierer erkennt, dass a = c - b , das a ja nicht verändert:

Lade a;
Addiere b; // ergibt c
subtrahiere b; // neues a, aber gleich altem a
....

Wenn er das erkannt hat, dann erkennt er aber auch dass c sofort überschrieben wird, dass also auch c = a + b nicht berechnet werden muss.
Ebenso verhält es sich mit * und /. Alle 4 Zeilen mit den Rechnungen verändern a und b nicht. Insgesamt kann man sich die 10^9 Durchläufe sparen, a und b bleiben bei den Anfangswerten.

Für dieses Testproblem braucht mein "Mensch-Compiler" zwar eine erhebliche "Compilezeit", aber meine "Mensch-CPU" hat in der Runtime nichts zu tun, I/O und Zeitmessung zählen ja nicht. In diesem Vergleichstest bin ich also mit 0 s der klare Sieger!!!!

Die Java-Zeiten mit- und ohne Optimierer lassen vermuten, dass Java irgendwie auch so eine Optimierung erkennt.

Mein Vorschlag: Lieber Standard-Algorithmen zur Berechnung von Zufallszahlen, Hashs oder Primzahlen nutzen.

Viel Erfolg,
Gruß Guaack
Bernhard Geyer
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 721
Erhaltene Danke: 3



BeitragVerfasst: Fr 24.04.15 23:31 
user profile iconC# hat folgendes geschrieben Zum zitierten Posting springen:
Jap da hat die JVM wohl optimiert. Ich habe die Optimierung ausgeschaltet mit dem Build- und JVM-Arg:
ausblenden Quelltext
1:
-Djava.compiler=NONE					


Jetzt braucht Java 20.440s :D. Das klingt schon mehr nach Java.

Und um es richtig zu vergleichen hast du auch bei C/C++ alle Optimierungen deaktiviert?

Java ist nicht langsam! Diese Aussage war noch zu Zeiten von Java 1.0 gültig als Java "Halbinterpretiert" ausgeführt wurde.
Mit aktuellen Java-Versionen braucht man sich nicht gegenüber C/C++ oder Delphi verstecken. Wir stellen gerade ein größere Anwendung nach Java um und können sagen das wir nicht langsamer werden. Je nach Situation ist die Delphi oder Java-Implementierung schneller.

In der c't gabs mal einen guten Vergleich zwischen C/C++, C#, Java und Delphi:
Kurzusammenfassung war: C/C++ ist im Bereuch Number-Crunshing unschlagbar. Geht es jedoch um "normale" Anwendungen die sehr viel Gebrauch Gebrauch von objektorientierten Techniken nimmt (Also nicht nur MFC-Like = WinAPI ohne Handle-Parameter) war C++ den anderen Programmiersprachen unterlegen.

Für diesen Beitrag haben gedankt: C#