Autor Beitrag
m.keller
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 133

Win xp, Win 7
C# (VS 2008)
BeitragVerfasst: Do 09.07.15 12:20 
Hallo,

ich stehe vor einer großen Frage wo ich auf eurer Wissen und Erfahrung angewiesen bin.

Verwendung von .Net 4.5 C#

Wenn ich z.b. 40 Millionen Werte miteinander in Schleifen berechnen will, ist es da Zeit Ersparnis wenn ich float anstatt double verwende?

Für mich klingt es an sich Sinnvoll, da float 4 Byte und double 8 Byte verwendet.
Und da es dauernd ein kopieren und lesen ist, kommt da bestimmt etwas an Zeit zusammen.

Hinzu kommt was passiert wenn es nur auf 32 bit oder auf 64 bit kompeliert wird?
zur Zeit kann ich nur auf 32 bit kompelieren aber die Software wird auf einem 64 bit Rechner laufen.
Hat das zusätzlich Auswirkung?

Danke schon einmal für die Anworten

_________________
Der gesunde Menschenverstand ist nur eine Anhäufung von Vorurteilen, die man bis zum 18. Lebensjahr erworben hat. (Albert Einstein)
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Do 09.07.15 12:54 
Zitat:
Hinzu kommt was passiert wenn es nur auf 32 bit oder auf 64 bit kompeliert wird?
zur Zeit kann ich nur auf 32 bit kompelieren aber die Software wird auf einem 64 bit Rechner laufen.
Hat das zusätzlich Auswirkung?


Du solltest nicht raten sondern das verhalten messen und vergleichen. Vorher kann man eigentlich nur spekulieren. Die Wahrscheinlichkeit ist hoch das es keinen Performanceunterschied gibt.
Christian S.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Do 09.07.15 13:01 
Meiner Erfahrung nach holt man mit solchen Änderungen Laufzeitverbesserungen im Prozentbereich (wenn überhaupt) heraus. Will man die Geschwindigkeit eines Programm wirklich verbessern, sollte man sich eher über Verbesserungen am Algorithmus Gedanken machen. Das hat bei mir bisher immer die durchschlagenderen Erfolge gebracht.

Wenn Speicher ein Problem wäre, würde das für die Verwendung von float sprechen, aber bei 40 Millionen Werten ist das wurscht.

Die Größe von float und double ist unabhängig von der Plattform definiert, sodass das Programm auf 32 Bit / 64 Bit identisch laufen wird.

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
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 09.07.15 13:29 
Hey,

habe mal einen kleinen Benchmark gemacht mit einfachen arithmetischen Operationen.
Eine Messung setzt sich aus der Generierung zweier Zufallszahlen und einer anschließenden Operation zusammen.
Die Generierung der Zahlen ist immer mit eingerechnet was wohl den größten Anteil an der Zeit ausmacht.
Da diese Funktion aber in jeder Messung gleich verwendet wird hab ich die nicht extra rausgenommen.

Hier der Benchmarkcode (falls es jemand selbst probieren will):

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:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
using System;
using System.Diagnostics;

namespace Test
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            long count = 1000000000;
            Random random = new Random();
            Stopwatch watch = new Stopwatch();

            watch.Restart();
            for (long i = 0; i < count; i++)
            {
                double a = random.Next(), b = random.Next();
                b = a / b;
            }
            watch.Stop();
            Console.WriteLine($"{count} double division with random numbers took {watch.Elapsed.TotalSeconds}s ({watch.Elapsed.TotalSeconds / count * 1e9 }ns per operation)");

            watch.Restart();
            for (long i = 0; i < count; i++)
            {
                double a = random.Next(), b = random.Next();
                b = a * b;
            }
            watch.Stop();
            Console.WriteLine($"{count} double multiplication with random numbers took {watch.Elapsed.TotalSeconds}s ({watch.Elapsed.TotalSeconds / count * 1e9 }ns per operation)");

            watch.Restart();
            for (long i = 0; i < count; i++)
            {
                double a = random.Next(), b = random.Next();
                b = a - b;
            }
            watch.Stop();
            Console.WriteLine($"{count} double subtraction with random numbers took {watch.Elapsed.TotalSeconds}s ({watch.Elapsed.TotalSeconds / count * 1e9 }ns per operation)");

            watch.Restart();
            for (long i = 0; i < count; i++)
            {
                double a = random.Next(), b = random.Next();
                b = a + b;
            }
            watch.Stop();
            Console.WriteLine($"{count} double addition with random numbers took {watch.Elapsed.TotalSeconds}s ({watch.Elapsed.TotalSeconds / count * 1e9 }ns per operation)");

            Console.WriteLine();

            watch.Restart();
            for (long i = 0; i < count; i++)
            {
                float a = random.Next(), b = random.Next();
                b = a / b;
            }
            watch.Stop();
            Console.WriteLine($"{count} float division with random numbers took {watch.Elapsed.TotalSeconds}s ({watch.Elapsed.TotalSeconds / count * 1e9}ns per operation)");

            watch.Restart();
            for (long i = 0; i < count; i++)
            {
                float a = random.Next(), b = random.Next();
                b = a * b;
            }
            watch.Stop();
            Console.WriteLine($"{count} float multiplication with random numbers took {watch.Elapsed.TotalSeconds}s ({watch.Elapsed.TotalSeconds / count * 1e9}ns per operation)");

            watch.Restart();
            for (long i = 0; i < count; i++)
            {
                float a = random.Next(), b = random.Next();
                b = a - b;
            }
            watch.Stop();
            Console.WriteLine($"{count} float subtraction with random numbers took {watch.Elapsed.TotalSeconds}s ({watch.Elapsed.TotalSeconds / count * 1e9}ns per operation)");

            watch.Restart();
            for (long i = 0; i < count; i++)
            {
                float a = random.Next(), b = random.Next();
                b = a + b;
            }
            watch.Stop();
            Console.WriteLine($"{count} float addition with random numbers took {watch.Elapsed.TotalSeconds}s ({watch.Elapsed.TotalSeconds / count * 1e9}ns per operation)");

            Console.ReadLine();
        }
    }
}


Resultat bei Any CPU (x86 bevorzugt):

ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
1000000000 double division with random numbers took 19,6086006s (19,6086006ns per operation)
1000000000 double multiplication with random numbers took 18,9227423s (18,9227423ns per operation)
1000000000 double subtraction with random numbers took 18,8146885s (18,8146885ns per operation)
1000000000 double addition with random numbers took 18,9843516s (18,9843516ns per operation)

1000000000 float division with random numbers took 19,760306s (19,760306ns per operation)
1000000000 float multiplication with random numbers took 19,5913353s (19,5913353ns per operation)
1000000000 float subtraction with random numbers took 19,7021056s (19,7021056ns per operation)
1000000000 float addition with random numbers took 19,5635708s (19,5635708ns per operation)


Resultat bei x86:
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
1000000000 double division with random numbers took 19,5128474s (19,5128474ns per operation)
1000000000 double multiplication with random numbers took 18,8952002s (18,8952002ns per operation)
1000000000 double subtraction with random numbers took 19,1500744s (19,1500744ns per operation)
1000000000 double addition with random numbers took 18,9657285s (18,9657285ns per operation)

1000000000 float division with random numbers took 19,7877776s (19,7877776ns per operation)
1000000000 float multiplication with random numbers took 19,5983161s (19,5983161ns per operation)
1000000000 float subtraction with random numbers took 19,7173869s (19,7173869ns per operation)
1000000000 float addition with random numbers took 19,599s (19,599ns per operation)


Resultat bei x64:
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
1000000000 double division with random numbers took 16,8016399s (16,8016399ns per operation)
1000000000 double multiplication with random numbers took 16,5740274s (16,5740274ns per operation)
1000000000 double subtraction with random numbers took 16,7004752s (16,7004752ns per operation)
1000000000 double addition with random numbers took 16,4864437s (16,4864437ns per operation)

1000000000 float division with random numbers took 16,6633615s (16,6633615ns per operation)
1000000000 float multiplication with random numbers took 16,6849258s (16,6849258ns per operation)
1000000000 float subtraction with random numbers took 16,7102619s (16,7102619ns per operation)
1000000000 float addition with random numbers took 16,7149567s (16,7149567ns per operation)


So wie es aussieht ist die x64 Plattform tatsächlich etwas schneller als die x86 wobei ich mir auch nicht erklären kann wieso, weil das ja eigentlich nix miteinander zu tun hat.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Christian S.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Do 09.07.15 13:37 
Auf einem 64-Bit-Rechner ist x64 IIRC auch schneller, weil für 32-Bit-Prozesse noch was emuliert werden muss. (Habe ich noch so ganz diffus im Gedächtnis :D)

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".

Für diesen Beitrag haben gedankt: C#
Horst_H
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1652
Erhaltene Danke: 243

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: Do 09.07.15 13:46 
Hallo,

vielleicht macht die Nutzung von SSEx.y bei float Werten einen Vorteil bringen, da doppelt soviele Elemente parallel verabeitet werden könnten.
www.yeppp.info/ dort docs.yeppp.info/cs/examples.html

Gruß Horst
P.S:
Zum Benchmark:
Freepascal nimmt bei x64 XMM Register statt fpu, vielleicht ist es hier ähnlich.
Ich würde erst die Zahlen erzeugen und dann die Operationen ausführen, denn Zuifallszahlen zu erzeugen ist etwas langwiriger als eine Addition.
Es ist doch sehr befremdlich das eine Division schneller wie eine Addition/Subtraktion sein soll (x64)
user32
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 55
Erhaltene Danke: 5



BeitragVerfasst: Sa 11.07.15 01:05 
user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
vielleicht macht die Nutzung von SSEx.y bei float Werten einen Vorteil bringen, da doppelt soviele Elemente parallel verabeitet werden könnten.
www.yeppp.info/ dort docs.yeppp.info/cs/examples.html

Na, dann doch erst recht Floats/Singles. Vierfach so viele Elemente parallel ist besser als zweimal so viele.
SSE Register haben eine Datenbreite von 128 Bit.
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: Sa 11.07.15 11:23 
Hier nochmal das x64 Ergebnis wenn die Zahlenerzeugung ausgelagert ist (musste jetzt 100 Mio Datensätze nehmen wegen OutOfMemoryException):
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
100000000 double division with random numbers took 0,6791924s (6,791924ns per operation)
100000000 double multiplication with random numbers took 0,6003383s (6,003383ns per operation)
100000000 double subtraction with random numbers took 0,5843254s (5,843254ns per operation)
100000000 double addition with random numbers took 0,5206888s (5,206888ns per operation)

100000000 float division with random numbers took 0,6574987s (6,574987ns per operation)
100000000 float multiplication with random numbers took 0,5592339s (5,592339ns per operation)
100000000 float subtraction with random numbers took 0,5563838s (5,563838ns per operation)
100000000 float addition with random numbers took 0,5360459s (5,360459ns per operation)

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Sa 11.07.15 11:54 
Zitat:
Na, dann doch erst recht Floats/Singles. Vierfach so viele Elemente parallel ist besser als zweimal so viele.
SSE Register haben eine Datenbreite von 128 Bit.


Wenn sie denn benutzt werden würden. Meinem Wissensstand nach geht das in .Net über die FPU heißt die werden in 80bit berechnet egal ob float oder double.
Die hier gezeigten Messwerte scheinen das ja auch nahezulegen das zwischen float/double eher keine echten Performanceunterschiede auftreten.

Wenn es Performanceunterschiede gibt hat das eher mit dem drum herum zu tun. Im gezeigten Test möglicherweise z.b. mit der Implementierung von Random oder den dabei nötigen casts.
Random.Next liefert ja einen Int und muß zu double/float gecastet werden. Die beiden casts könnten unterschiedlich schnell sein.
Mann könnte jetzt auch direkt NextDouble verwenden. Aber dann hat man weiterhin zumindest den cast zu float.

Worauf ich hinaus will, ein künstlicher Test kann keinerlei Aussage treffen über das Performanceverhalten des konkret verwendeten Codes des TE. Casting zwischen Datentypen (die Oberfläche vom Framework ist eigentlich immer double nie float), möglicherweise typspezifisches Boxingverhalten oder was der Teufel wird wenn ich einen scheinbar simplen Vorgang einfach millionenfach wiederhole sehr relevant. Das zu übersehen ist sehr einfach darum wäre ich vorsichtig Schlüsse anhand von Messungen zu ziehen die nichts mit dem eigentlichen Code zu tun hat. Wenn ich Informationen über das Laufzeitverhalten meines Codes brauche dann sollte ich auch genau diesen Code messen. Anderer Code kann das nicht sinnvoll ersetzen sondern liefert maximal Anhaltspunkte auf die man achten sollte.

Für diesen Beitrag haben gedankt: C#
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: Sa 11.07.15 13:08 
Ich bin auch immer sehr skeptisch was Benchmarks angeht die nur auf flops abzielen. Aber bei meinem zweiten Post hier habe ich das ganze Randomverfahren nach außen gezogen und die Zahlen vorab erstellt und in einem Array gespeichert. Einmal double und einmal float, d.h. in dem zweiten Ergebnis wird nur die Rechenoperation (inklusive der for-Schleifen) gemessen, da ist Random komplett außen vor.

Das die FPU mit 80 Bit rechnet wurde bei uns in der Vorlesung auch schon erwähnt. Jetzt weiß ich auch, warum in .NET alles mit double gemacht wird (Danke Ralf). Was mich aber zu der Frage führt warum die Graphics-Klasse bei WinForms mit float arbeitet? Steckt da ein Wrapper dahinter der nicht-.NET Code einbindet?

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Sa 11.07.15 13:44 
Zitat:
Das die FPU mit 80 Bit rechnet wurde bei uns in der Vorlesung auch schon erwähnt. Jetzt weiß ich auch, warum in .NET alles mit double gemacht wird (Danke Ralf). Was mich aber zu der Frage führt warum die Graphics-Klasse bei WinForms mit float arbeitet? Steckt da ein Wrapper dahinter der nicht-.NET Code einbindet?


Meine Begründung warum (fast) alles double ist wäre eine andere gewesen. Und zwar das das Framework ja möglichst sprachunabhängig sein sollte und nicht jede Sprache mehrere Floatingpoint typen kennt. Die gleiche Grund warum fast nirgendwo unsigned Typen verwendet werden. Nicht jede Sprache kennt das. Das passt natürlich schlecht zu deiner Beobachtung mit der Graphics Klasse. Ich vermute mal das das gekapselte GDI+ Zeug exclusiv mit einfacher Genauigkeit arbeitet und daher man auch gleich float genommen hat. Da gerade zeichnen oft performancekritisch ist hat man sich gespart da einen Schicht mit Typumwandlung zwischen zuschalten (ein vermutlich teurer nativer single nach managed double cast).

Falls es aus dem Schreibstil nicht deutlich wird. Das gesagte ist alles rein spekulativ ;)

Zitat:
Aber bei meinem zweiten Post hier habe ich das ganze Randomverfahren nach außen gezogen und die Zahlen vorab erstellt und in einem Array gespeichert.


Dann ist man bald dabei das ein intelligenter Compiler das merkt und die vielen Millionen Schleifen nicht mehr durchführt sondern durch äquivalenten schnelleren Code ersetzt. Aka "Moment der rechnet 100.000.000 Millionen mal +1 da kann ich auch gleich +100.000.000 rechnen". Es bleibt dabei ein Benchmark der etwas ganz simples ganz oft durchführt hat immer ein hohes Risiko einen eigentlich ungewollten Nebeneffekt zu messen und denn viel zu hoch zu bewerten. Da muss man schon ganz viel internes Wissen haben um sich nicht zu tölpeln ;) Ich würde mir da selbst nicht all zuweit trauen und lieber was problemnahes (am besten gleich das Problem) benchmarken.


Edit: Ein potentieller Nebeneffekt der mir gerade einfällt wo du das sagst
Zitat:
und in einem Array gespeichert

und die Zahlen vorab erstellt. Vorher konnten die Variablen vermutlich komplett in Registern gehalten werden (in den 80bit breiten FPU Registern egal ob 32 oder 64 bit) mit einem Array liegen die im RAM. Wir bekommen jetzt alle ein RAM -> Register verschiebe Faktor rein. Die Bus breite könnte jetzt also einen größeren Einfluss haben und sich im Fall 64bit Typ in einem 32bit Prozess stark auswirken. Oder auch nicht wenn das Caching Verhalten der CPU dazwischen funkt. Das verschieben von Daten zwischen RAM und CPU Cache ist vermutlich unabhängig von der Bittigkeit des Prozesses und findet immer mit 64bit Operationen statt.

Für diesen Beitrag haben gedankt: C#