Laden...

Performancebetrachtungen Fortran - C#

Erstellt von gfoidl vor 14 Jahren Letzter Beitrag vor 13 Jahren 22.146 Views
gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren
Performancebetrachtungen Fortran - C#

[EDIT] Abgetrennt aus Was wünscht ihr euch für C# 5?[/EDIT]

Schön wäre auch wenn C# bzw. .NET endlich mal in der Geschwindigkeit mit nativen Anwendungen konkurrieren könnte.

Hast du dazu einen aktuellen Benchmark?

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

1.130 Beiträge seit 2007
vor 14 Jahren

Hast du dazu einen aktuellen Benchmark?

Das ist zwar kein richtiger benchmark, aber schon erstaunlich:

Ein bekannter hat mal eine funktion, die einen float mit 3 multipliziert in c++ geschrieben und über pinvoke importiert.
In etwa so sah der Test aus.

float pi=Math.Pi;
maldrei(0);//erste Aufrufe sind langsam
Stopwatch sw=new Stopwatch();
sw.Start();
sw.Stop();
sw.Reset();

{
sw.Start();
float result=3*pi;
sw.Stop();
//ausgabe
sw.Reset();
}
{
sw.Start();
float result=maldrei(pi);
sw.Stop();
//ausgabe
sw.Reset();
}

Ergebnis: Die von c++ importierte funktion hat grundsätzlich ca. 12% weniger Zeit gebraucht! (außer beim ersten aufruf)

Projekte:Jade, HttpSaver
Zum Rechtschreiben gibts doch schon die Politiker. Aber die bauen auch nur mist!

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Wenn es tatsächlich dieser Test war kann ich das nicht glauben. Der JITer würde erkenne dass 3 * pi konstant ist und es somit als Konstante ersetzen. Der C++-Aufrug muss jedoch durchgeführt werden. Somit muss irgendein Nebeneffekt vorhanden gewesen sein.

Muss mal schauen wenn ich Zeit werde ich mal einen Benchmark bezüglich numerischer Probleme mit C# vs. Fortran 95 anstellen. Schauen wie weit C# an den "Klassenprimus" herankommt.

Edit: offtopic (grau entfernt).

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

1.130 Beiträge seit 2007
vor 14 Jahren

Ich hatte ihm auch folgendes empfohlen, aber er meinte, es macht keinerlei Unterschied:

object refPi=(float)Math.Pi;
Gc.KeepAlive(refPi);
float pi=(float)refPi;

Ich glaube aber, er hatte die beiden klammern in anderer Reigenfolge. (erst c++ und dann c# test)

Projekte:Jade, HttpSaver
Zum Rechtschreiben gibts doch schon die Politiker. Aber die bauen auch nur mist!

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Muss mal schauen wenn ich Zeit werde ich mal einen Benchmark bezüglich numerischer Probleme mit C# vs. Fortran 95 anstellen.

Hab mal einen Vergleich von Fortran 95 (Compiler von Lahey Fujits v7.2) mit C# (.net 3.5 SP1) angestellt.

Verglichen wurde:*Matrix-Multiplikation 1000x1000 * 1000x1000, Datentyp double: In Fortran wurde der sprachintegrierte Befehl verwendet und in C# eine von mir optimierte Variante der Matitzenmultiplikation (mit C# gehts nicht mehr schneller 😁 **). In C# wurde ebenfalls eine paralle Variante der Matrixmultiplikation getestet. Für Fortran war dies nicht möglich da ich dafür keinen Compiler habe (diese sind sauteuer).

*N-dimensionale Monte-Carlo Integration von exp(-t^2/2) über [0,1] x [0,1] x ... x [0,1] wobei für die Dimension N := 10 gewählt wurde.

Im angehängten Bild der Vergleich. Die Ergebnisse wurden jeweil auf die schneller Sprache (Fortran) normiert.

Dass der Unterschied so extrem (C# 2,5...4x langsamer) ist hätte ich mir nicht gedacht.
Man darf dabei nicht vergessen dass das nur ein Test für eine isolierte Berechnung ist. In der Realität kommt noch mehr hinzu: Eingabe - Verarbeitung - Ausgabe. Hier wurde nur die Verarbeitung (vorhandener Daten) getestet. Ich glaube dass über den Gesamtzyklus C# die Nase vorn hat.

Als ich den Benchmark-Code von Fortran nach C# übersetzt habe vermisste ich Fortrans Tensor-Operatoren ziemlich. Zur Verdeutlichung:
In Fortran ist für die Initialisierung einer Matrix ein Befehl notwendig: matrix = 1. In C# müssen beide Schleifen explizit programmiert werden. Wenn ich mich nicht täusche hab ich mir das schon gewünscht 😉

Edit: Test mit NGen hinzugefügt.
Edit: offtopic (grau entfernt).

mfG Gü

** PS - Edit: ich hob doch noch eine (leicht) schnellere Variante (für x64) gefunden und zwar indem eine Matrix transponiert wird und somit die Elemente als Skalarprodukt (Achtung auf Indizes) berechnet werden kann. Dies ermöglicht dann auch noch schnellere SIMD-Wege.

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

1.346 Beiträge seit 2008
vor 14 Jahren

Lass das mal auf der GPU laufen. Kann man in C# machen und ist, bei einer großen Matrix, bestimmt um einiges schneller(meine GPU schafft 566Gigaflops). Es gibt einn Microsoft Research Projekt Accelerator. Ist einfach zu verwenden und genau für sowas gedacht. 8)

Gruß pdelvo

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Ich weiß. Accelarator (bzw. jede GPU) unterstützt (momentan) nur floats und für die Numerik sind nun mal doubles eher der Fall.
Außerdem wäre dann der Vergleich der Sprachen nicht mehr gegeben. Auf die GPU könnte ich auch mit Fortran zugreifen (wenns mit C geht gehts auch mit Fortran). Weiters könnte Fortran noch parallel getestet werden (da sollte die Zeit nochmals auf die 1/2. sinken) - wäre aber immer noch langsamer als die GPU. Abe das hat nichts mit mehr mit der Sprache zu tun. Der nächste Schritt wäre den Vergleich auf einem Hochleistungcluster durchzuführen 😉

Edit: offtopic (grau entfernt).

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

5.742 Beiträge seit 2007
vor 14 Jahren

Hallo gfoidl,

wie oft hast du gemessen?

Hoffentlich ohne Debugger?
NGEN?!?

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Gemessen wurde so:
Natürlich ohne Debugger - sonst macht ja keinen Sinn 😉1.Durchlauf ohne Zeitmessung (für laden aller Teile sowie JITen) 1.5 Durchläufe mit Zeitmessung und vor jedem Durchlauf eine GC durchgeführt (nur bei der C#-Variante) 1.Die Zeit der 5 Durchläufe wurde gemittelt.

NGen hab ich nicht getestet - glaube aber nicht dass das einen Vorteil bringt denn nach dem "Leerdurchlauf" liegt bei der C#-Varainte auch Maschinencode vor. NGen bringt AFAIK nur beim Startverhalten wesentlich was und das wurde in der Messung ja nicht berücksichtigt.

Weiters hab ich den Test auf 2 Rechnern durchgeführt (1x DuoCore, 1X SingleCore) und das Ergebnis für den jeweiligen Benchmark von dem Rechner genommen für den die insgesamt bessere Zeit erziehlt wurde (war bei der Matrixmultiplikation der SingleCore - obwohl ich glaube das ist deshalb weil ich auf diesem Rechner sonst nichts laufen habe und somit alle Ressourcen verwendet werden können - während beim DualCore-Rechner hab ich ein paar Instanzen von VS offen,....).[/color]

Edit: Ausgegraut für offtopic.
Edit: offtopic (grau entfernt).

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

5.742 Beiträge seit 2007
vor 14 Jahren

Die Zeit der 5 Durchläufe wurde gemittelt.

Wieso gemittelt?
IMHO wäre es sinnvoller, den besten zu verwenden.

NGen hab ich nicht getestet - glaube aber nicht dass das einen Vorteil bringt denn nach dem "Leerdurchlauf" liegt bei der C#-Varainte auch Maschinencode vor.

Nein, zumindest ab Version 3.5 soll NGEN anscheinend weitere Optimierungen vornehmen.

//EDIT:
Achso: 32bit oder 64bit?

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Wieso gemittelt? IMHO wäre es sinnvoller, den besten zu verwenden.

Wenn schon müsste der max/min/avg und die Standardabweichung angegeben werden um ein statischtische Repräsentation der Daten zu erhalten. Mir geht es aber eher um den qualitativen Vergleich und dazu reicht der Mittelwert. Bei den Unterschieden wird es kaum etwas ausmachen ob der min- oder avg-Wert genommen wird.

Nein, zumindest ab Version 3.5 soll NGEN anscheinend weitere Optimierungen vornehmen.

Hab das Diagramm aktualisert. NGen bringt nur minimal was - kann also fast vernachlässigt werden.

Achso: 32bit oder 64bit?

32 bit - ich hab keine Fortran 64 bit Compiler (und auch keinen 64 bit Rechner). Das Verhalten wird sich jedoch kaum ändern wenn sowohl Fortran als auch C# auf 64 bit läuft.

Edit: offtopic (grau entfernt).

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

5.742 Beiträge seit 2007
vor 14 Jahren

Hmm - verdammt: Mir fällt nichts mehr ein, was du falsch gemacht haben könntest 😁
Die Unterschiede sind tatsächlich mehr als deutlich.

Interessant wäre ein Vergleich mit C++ - Fortran scheint ja sehr optimiert für numerische Berechnungen zu sein.

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Mir fällt nichts mehr ein, was du falsch gemacht haben könntest

Ich glaub da gibts auch nicht viel. Der Code beider Varianten wurde auch manuell optimiert. Bei C# wurde vor allem auf die Range-Check-Elimination geachtet (die bringt sehr viel - dadurch kann die Schleife bis 20x schneller werden!).

Die Unterschiede sind tatsächlich mehr als deutlich.

Dass es so gravierend ist hätte ich mir auch nicht gedacht.
Da merkt man den Unterschied zwischen statischer Kompilierung bei der mehr Möglichkeiten zur Optimierung (Algebraic simplifications, Transform array element to simple variable, Local Instruction scheduling, Address calculation optimization, Array optimization, Loop unrolling/interchanging, Inlining mathematical functions, Stack optimization - um nur ein paar Schlagworte zu nenen) gegenüber dynamischer Kompilierung mit dem JITer der für die Optimierung ein recht simple Heuristik verwendet.
Warum die NGen-Variante ebenfalls relativ schlecht abschneidet wundert mich. Da könnten doch die oben genannten Optimierungen durchgeführt werden.

Interessant wäre ein Vergleich mit C++ - Fortran scheint ja sehr optimiert für numerische Berechnungen zu sein.

Fortran steht ja für "Formula translation". Die Sprache kann relativ wenig dafür dies aber sauschnell - und das sind vor allem Tensoroperationen die zum Großteil Sprachbestandteil sind.
C++ kann ich nicht und daher kann ich auch keinen Vergleich geben. Ich kann nur soviel dazu sagen dass die meisten numerischen Programme in Fortran geschrieben sind bzw. mit Fortran erstellt Libraries verwenden. Wenn du Vergleiche im Internet findest muss darauf geachtet werden ob der Test mit den Sprachmitteln oder eine Numerik-Libraray durchgeführt wurde. Es wird nämlich gerne Standard-Fortran mit einer optimierten C-Library verglichen. Das gefinkelte dabei ist dass diese C-Libraries oft eine in Assembler optimierte Fortran-Library verwenden (das wird im Test aber nicht erwähnt damit C++ besser da steht).
Diese optimierten Fortran-Libraries sind geschwindigkeitsmäßig der Hammer. Ich hab mal meinen in Fortran geschrieben Code zur Lösung von linearen Gleichungssystemen (ich glaube ich hab die Gauß-Seidel-Iteration verwendet) gegen dieselbe Variante aus den Scientific Subroutine Library antreten lassen und wurde kläglich geschlagen (ich glaube es war so um Faktor 20).
Eine Interessante Anmerkung zu C++ vs. Fortran gibt: Fortran 90 for Science Students

Dennoch - wie bereits vorhin mal erwähnt - hat C# einige Vorteile und ist nicht so langsam wenn der "Betrachtungsradius" vergrößert wird.
Geht es darum Daten zu besorgen (zB von einer Datenbank) und diese danach grafisch darzustellen liegt C# klar im Vorteil. Wenn somit der gesamte Zyklus (in einer realen Anwendung) geprüft wird kann ich mir gut vorstellen dass C# "schneller" ist. Das ist auch der Grund warum ich vor ca. 1,5 Jahren von Fortran zu C# gewechselt bin.

Vom eigentlichen Thema sind wird ziemlich weit abgerutscht. Vielleicht kann ein Moderator ein eigenes Thema daraus machen.

Edit: offtopic (grau entfernt) - Dank an den Moderator.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

5.742 Beiträge seit 2007
vor 14 Jahren

Vom eigentlichen Thema sind wird ziemlich weit abgerutscht. Vielleicht kann ein Moderator ein eigenes Thema daraus machen.

Ja, ich glaube das wäre sinnvoll.

Da fällt mir ein: Gibt es nicht auch einen Fortrancompiler, der IL-Code erzeugt?

Zum Vergleich mit C++:
Kannst du eventuell deinen C# mal hier posten? Dann findet sich sicherlich jemand, der den nach C++ übersetzen kann - bei rein mathematischem Code sollte das ja nicht so riesige Unterschiede machen.
Dann würde man nämlich mal sehen, wie viel sich - alleine durch Compileroptimierungen - bei diesem speziellen Algorithmus sich noch "aus C# rausholen" lassen würde.
Evtl. hat hier ja auch irgendwo jemand einen Intel-C++-Compiler rumliegen; der soll ja nochmal besser als der MS Compiler optimieren (stand zumindest mal in der c't).

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Gibt es nicht auch einen Fortrancompiler, der IL-Code erzeugt?

Ja die gibt es. Die Geschwindigkeitsvorteile sind dann aber weg denn der JITer ist der "kritische" Compiler und der ist dann derselbe.

Was ich beim Test (aus Faulheit) nicht berücksichtigt habe ist dass der Fortran-Teil auch noch Konsolenausgaben durchführt. Hab den Test nochmals gemacht

Kannst du eventuell deinen C# mal hier posten?

Ja kann ich. Wobei ich noch anmerken will dass ich eine ganze Sammlung von Fortran-Benchmarks habe und aus denen hab ich den Klassiker (Matritzenmultiplikation) und den kürzesten genommen (Monte-Carlo-Integration). Alle anderen waren mir zu aufwändig um diese zu übersetzen (2000+ Zeilen 😦).

Klassendiagramm angehängt - Achtung die Schnittstelle beeinflusst die Messung nicht!


namespace Benchmark
{
	public interface IBenchmark
	{
		void Run();
		long Milliseconds { get; }
		string Name { get; }
	}
}


namespace Benchmark
{
	class Program
	{
		static void Main(string[] args)
		{
			List<IBenchmark> benchmarks = new List<IBenchmark>
			{
				new Matmul(),
				new MonteCarlo()
			};

			foreach (IBenchmark benchmark in benchmarks)
			{
				GC.Collect();
				GC.WaitForPendingFinalizers();

				benchmark.Run();
				Console.WriteLine(
					"{0}\t{1}",
					benchmark.Name,
					benchmark.Milliseconds);
			}

			Console.ReadKey();
		}
	}
}

Matritzenmultiplikation
Die Elemente der Matrix werden im Speicher sequentiell angeordnet. Dabei gibt es zwei Möglichkeiten für die Speicherung:*splatenweise (Spaltenvektoren) *zeilenweise (Zeilenvektoren)

In Fortran und verwandten Sprache wie Matlab wird die Matrix in Form von Spaltenvektoren gespeichert während C-artige Sprachen Zeilenvektoren speichern. C# speichert eine Matrix die mit T[,] erstellt wird ebenfalls zeilenweise. Diese Variante der rectangular Arrays ist jedoch langsamer als die Variante mit jagged Arrays. Der Grund liegt darin dass es eigene IL-Anweisungen für zero-based one dimensional Arrays gibt und diese effizient* umgesetzt werden. Beim rectangular Array erfolgt der Zugriff auf die Elemente mit Methoden die ein object zurückgeben. Daher kommt das Castingproblem eventuell auch noch dazu.

Spaltenvektor oder Zeilenvektor?
Ich halte mich dabei an die Herangehesnweise von Fortran nämlich die spaltenweise Speicherung. Warum? Zum einem kann eine Matrix verallgemeinert als Tensor 2. Stufe betrachtet werden. Ein Tensor 0. Stufe wäre ein Skalar (also eine Zahl) und ein Tensor 1. Stufe ein (Spalten-) Vektor. Somit halte ich mich an den Tensor-Begriff. Der Tensor 1. Stufe ist bewusst nicht als Zeilenvektor defniert denn in der Mathematik werden Spaltenvektoren statistisch gesehen häufiger benötigt als Zeilenvektoren - aber das ist eine andere Geschichte.
Nun ist es mit jagged Arrays möglich eine Matrix ebenfalls spaltenweise zu speichern. Dabei ändert sich nur die Reihenfolge der Indizes - dies kann jedoch über einen Indexer der die Position berechnet nach außen hin wurscht sein. Für die meisten Problem könnte das auch egal sein denn jede Matrix-Operation kann von zeilen- auf spaltenweise umgeschrieben werden. Bei komplizierten Formeln wie sie zB in der Simulation von kompressiblen Medien auftritt kann das aber zu einem ziemlichen Unterfange ausarten. Daher ist es praktisch die gegeben Formeln effizient zu Verwenden indem eine Matrix Spaltenweise gespeichert wird.
Ich hab mal getestes ob bei der Matrixmultiplikaton ein Unterschied zwischen Zeilen-/Spaltenvektor-Variante besteht und die Speicherung mit Spaltenvektor erwies sich auch hier als performanter.

* ist im Vergleich zu Fortran relativ 😉

Für den Test werden zwei 1000x1000 Matritzen (die mit zufälligen Werten gefüllt sind) multipliziert.


using System;
using System.Diagnostics;
using gfoidl.Parallelization;  // hab das TPL nicht verwendet sondern meine eigene denn die ist (bisher) schneller

namespace Benchmark
{
	/// <summary>
	/// Matmul 1000x1000 * 1000x1000.
	/// </summary>
	public class Matmul : IBenchmark
	{
		const int N = 1000;
		private double[][] _a, _b, _c;
		//---------------------------------------------------------------------
		#region IBenchmark Member
		public void Run()
		{
			CreateMatrices();

			// JITen:
			DoMatmul(_a, _b, _c);

			// Test:
			Stopwatch sw = new Stopwatch();
			sw.Start();
			for (int i = 0; i < 5; i++)
				DoMatmul(_a, _b, _c);
			sw.Stop();
			_milliSeconds = (long)(sw.ElapsedMilliseconds * 0.2);
		}
		//---------------------------------------------------------------------
		private long _milliSeconds;
		public long Milliseconds
		{
			get { return _milliSeconds; }
		}
		//---------------------------------------------------------------------
		public string Name
		{
			get { return "Matmul"; }
		}
		#endregion
		//---------------------------------------------------------------------
		private void CreateMatrices()
		{
			_a = new double[N][];
			_b = new double[N][];
			_c = new double[N][];
			Random rnd = new Random();

			for (int j = 0; j < N; j++)
			{
				_a[j] = new double[N];
				_b[j] = new double[N];
				_c[j] = new double[N];

				for (int i = 0; i < N; i++)
				{
					_a[j][i] = rnd.NextDouble();
					_b[j][i] = rnd.NextDouble();
				}
			}
		}
		//---------------------------------------------------------------------
		private void DoMatmul(double[][] a, double[][] b, double[][] c)
		{
			for (int j = 0; j < b.Length; j++)
			{
			//Parallel.For(0, b.Length, j =>
			//{
				// Cachen damit die Speicherzugriffe optimiert werden können
				// und der Compiler Range-Check-Eliminatin anwenden kann.
				// Das bringt einen großen Performance-Vorteil (v.a. im 
				// Vergleich zur klassischen Variante (unten)).
				double[] bColj = b[j];
				double[] cColj = c[j];

				for (int k = 0; k < a.Length; k++)
				{
					// Cachen:
					double[] aColk = a[k];
					double b_kj = bColj[k];

					for (int i = 0; i < aColk.Length; i++)
						cColj[i] += aColk[i] * b_kj;
				}
			}
			//});

			// Klassische Implementierung der Matrixmultiplikation:
			//for (int i = 0; i < a.Length; i++)
			//    for (int j = 0; j < b.Length; j++)
			//    {
			//        double tmp = 0;
			//        for (int k = 0; k < a.Length; k++)
			//            tmp += a[i][k] * b[k][j];

			//        c[i][j] = tmp;
			//    }
		}
	}
}

Range-Check-Elimination (RCE) wird vom Compiler dann angewandt wenn das Array im lokalen Stack der Methode ist denn somit kann der Compiler verfolgen wie/ob das Array geändert wird und dementsprechend muss nicht bei jedem Zugriff eine Prüfung der Indexgrenzen durchgeführt werden. Somit kann mit RCE der Zugriff der Elemente in einer Schleife laut meinen Untersuchungen doppelt so schnell erfolgen. Das fällt allerdings weniger ins Gewicht wenn der Schleifenrumpf aufwändig ist.

Zum Vergleich der verwendete Fortran-Code (man beachte wie wenig Code für die Erstellung und Multiplikation der Matritzen notwendig ist):


program matrix
	implicit none
   	real(8), allocatable 		:: a(:,:), b(:,:), c(:,:)
	integer  					:: N = 1000, i
   	real     					:: t0, t1

	allocate( a(N,N), b(N,N), c(N,N) )
   	call random_seed()
   	call random_number(a)
   	call random_number(b)

	call cpu_time(t0)
	do i = 1, 5
   		c = matmul(a,b)
   	end do
   	call cpu_time(t1)
   	write(*,'(a, f10.3)') ' Time for matmul        = ', (t1 - t0) / 5
end program matrix

Monte-Carlo Integration
Es wird eine N-dimensionale Integration der Funktion f(x) = exp(-x^2/2) in den Grenzen [0,1] x [0,1] x ... x [0,1] durchgeführt. N := 10.
Da ich mir den Test nicht ausgedacht habe und die Originalfassung in Fortran eine Lizenzinformation enthält kommt diesmal der Fortran-Code zuerst. In der Originalfassung sind sogar noch die Konsolenausgaben enthalten!


program monte_carlo_integration
!
!       peform an N dimensional integration of exp(-t**2/2)
!       over the square [0,1] x [0,1] x ... x [0,1]
!       for N dimensions using a Monte Carlo scheme.
!
!
!=========================================================================
!
!  This code is part of the Quetzal Computational Associates Fortran 90
!  Benchmark Suite.
!
!  Copyright (c) 1994 by Quetzal Computational Associates.
!
!  Permission to copy and use without fee all or part of the
!  publications or computer codes in this suite is granted provided 
!  that copies are not made or distributed for direct commercial
!  advantage, this copyright notice and the title of the codes
!  or publications and the fact that they originated in this
!  benchmark suite appear, and notice is given that copying is
!  by permission of Quetzal Computational Associates.  For
!  publications that have been published in journals, the name
!  of the publication and date should appear.  To copy otherwise,
!  or to republish, requires a fee and/or specific permission.
!  Authors of codes in this benchmark suite are exempt from the terms of
!  this copyright notice for their codes only.  Address any
!  questions about this copyright notice to:
!
!         John K. Prentice
!         Quetzal Computational Associates
!         3200 Carlisle N.E.
!         Albuquerque, NM   87110-1664
!         USA
!
!         Phone:  505-889-4543
!         Fax:    505-889-4598
!         E-mail: [EMAIL]quetzal@aip.org[/EMAIL]
!
!=========================================================================
!
    implicit none
    integer, parameter :: problem_dimension = 10 
    integer, parameter :: number_of_trys = 6 
!
    integer :: k, number_of_integration_points
    real :: integral, exact_answer
    real, dimension(:,:), allocatable :: integration_points 
    real, dimension(:), allocatable :: argument
    real :: exact_answer_in_1d = 0.85562439
    integer, dimension(number_of_trys) :: try_size 
    
    real     					:: t0, t1
!
    data try_size/9000, 100, 60000, 423, 100000, 2533/ 
    
    call cpu_time(t0)
    
!
!       loop over different trys
!
    do k = 1, number_of_trys 
      number_of_integration_points = try_size(k) 
!
!       allocate memory 
!
      allocate (integration_points(number_of_integration_points,problem_dimension))
      allocate (argument(number_of_integration_points))
!
!       randomly calculate integration points
!
      call gleich
!
!        do Monte Carlo integration
!
      argument = sum(0.5*integration_points**2,dim=2) 
      integral = sum(exp(-argument)) / real(number_of_integration_points)
!
      exact_answer = exact_answer_in_1d**problem_dimension 
      print *, ' ' 
      print *, 'n = ', problem_dimension, ' number of points = ', &
     &      number_of_integration_points 
      print *, 'exact answer       = ', exact_answer 
      print *, 'Monte Carlo answer = ', integral 
!
      deallocate (integration_points)
      deallocate (argument)
!
    end do 
    
    call cpu_time(t1)
    write(*,'(a, f10.3)') ' Time for montecarlo        = ', t1 - t0
!
contains
!
    subroutine gleich
!
!        this routine by t. t. warnock, cray research analyist at lanl
!        F77 version written about 1980
!
!
    implicit none
!
    real, dimension(number_of_integration_points,problem_dimension) :: bottom, top, radish_value
    integer, dimension(number_of_integration_points,problem_dimension) :: radix_value, spin_value, &
&                                                                         quo, errors
    integer :: kount
!
    integer, dimension(25) :: radix, spin 
    real, dimension(25) :: radish 
!
    data radix/2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,&
&              61, 67, 71, 73, 79, 83, 89, 97/ 
    data radish/2., 3., 5., 7., 11., 13., 17., 19., 23., 29., 31., 37., 41.,  &
&               43., 47., 53., 59., 61., 67., 71., 73., 79., 83., 89., 97./ 
    data spin/1, 2, 1, 5, 3, 8, 2, 7, 18, 11, 18, 3, 17, 24, 40, 15, 40, 49,  &
&             12, 30, 40, 70, 9, 39, 82/ 
!
    top = 0. 
    bottom = 1. 
    do kount = 1,number_of_integration_points
        radish_value(kount,1:problem_dimension) = radish(1:problem_dimension)
        spin_value(kount,1:problem_dimension) = spin(1:problem_dimension)
        radix_value(kount,1:problem_dimension) = radix(1:problem_dimension)
        quo(kount,1:problem_dimension) = kount
    enddo
!
10  where (quo /= 0)
        top = top * radish_value + mod(quo * spin_value, radix_value)
        bottom = bottom * radish_value
        quo = quo / radix_value
        errors = 1
    elsewhere
        errors = 0
    end where
    if (sum(errors) > 0) go to 10
!
    integration_points = top/bottom 
!
    end subroutine gleich 
!
end program monte_carlo_integration

Mein Portierung dessen nach C# schaut wie folgt aus (wiederum beachte man wie wenig Code für manche Operationen in Fortran notwendig ist und in C# geschleift wird). Es wurden wiederum jagged Array verwendet und auf Range-Check-Eliminatin geachtet (um 20% schneller als ohne RCE).
Wegen der Lizenzinfo bei Fortran: Hab ich jetzt die Lizenz für die C#-Variante? 😉


using System;
using System.Diagnostics;

namespace Benchmark
{
	/// <summary>
	/// N dimension integration of exp(-t^2 / 2) over the square
	/// [0,1] x [0,1] x ... x [0,1]
	/// for 10 Dimensions using Monte Carlo scheme.
	/// </summary>
	public class MonteCarlo : IBenchmark
	{
		private const int PROBLEM_DIMENSION = 10;
		private const int NUMBER_OF_TRIES = 6;
		private int _numberOfIntegrationPoints;
		private double[][] _integrationPoints;
		//---------------------------------------------------------------------
		#region IBenchmark Member
		public void Run()
		{
			// JITen:
			DoMonteCarlo();

			// Test:
			Stopwatch sw = new Stopwatch();
			sw.Start();
			for (int i = 0; i < 5; i++)
				DoMonteCarlo();
			sw.Stop();
			_milliSeconds = (long)(sw.ElapsedMilliseconds * 0.2);
		}
		//---------------------------------------------------------------------
		private long _milliSeconds;
		public long Milliseconds
		{
			get { return _milliSeconds; }
		}
		//---------------------------------------------------------------------
		public string Name
		{
			get { return "Monte Carlo"; }
		}
		#endregion
		//---------------------------------------------------------------------
		private double DoMonteCarlo()
		{
			double errorOfIntegral=double.MaxValue;
			double integral = 0d;
			double exactAnswerIn1d = 0.8562439;
			int[] trySize = { 9000, 100, 60000, 423, 100000, 2533 };

			for (int k = 0; k < NUMBER_OF_TRIES; k++)
			{
				_numberOfIntegrationPoints = trySize[k];
				double[] argument = new double[_numberOfIntegrationPoints];
				_integrationPoints = new double[_numberOfIntegrationPoints][];

				// Lokale Kopie für Range-Check-Elimination:
				double[][] localIntegrationPoints = _integrationPoints;
				for (int i = 0; i < localIntegrationPoints.Length; i++)
					localIntegrationPoints[i] = new double[PROBLEM_DIMENSION];

				// Integrationspunkte berechnen:
				Gleich();

				for (int j = 0; j < argument.Length; j++)
				{
					double[] integrationPointsColj = localIntegrationPoints[j];
					for (int i = 0; i < integrationPointsColj.Length; i++)
						argument[j] += 
							0.5 * integrationPointsColj[i] * integrationPointsColj[i];

					integral += Math.Exp(
						-argument[j]) /
						_numberOfIntegrationPoints;
				}

				double exactAnswert = Math.Pow(exactAnswerIn1d, PROBLEM_DIMENSION);
				errorOfIntegral = exactAnswert-integral;
			}

			// Verhindert dass der Compiler die Variablen wegoptimiert:
			return errorOfIntegral;
		}
		//---------------------------------------------------------------------
		private void Gleich()
		{
			// Range-Check-Elimination beachten!
			
			double[][] bottom = new double[_numberOfIntegrationPoints][];
			for (int i = 0; i < bottom.Length; i++)
			{
				bottom[i] = new double[PROBLEM_DIMENSION];
				double[] local = bottom[i];
				for (int j = 0; j < local.Length; j++)
					local[j] = 1;
			}

			double[][] top = new double[_numberOfIntegrationPoints][];
			for (int i = 0; i < top.Length; i++)
				top[i] = new double[PROBLEM_DIMENSION];

			double[][] radishValue = new double[_numberOfIntegrationPoints][];
			for (int i = 0; i < radishValue.Length; i++)
				radishValue[i] = new double[PROBLEM_DIMENSION];

			int[][] radixValue = new int[_numberOfIntegrationPoints][];
			for (int i = 0; i < radixValue.Length; i++)
				radixValue[i] = new int[PROBLEM_DIMENSION];

			int[][] spinValue = new int[_numberOfIntegrationPoints][];
			for (int i = 0; i < spinValue.Length; i++)
				spinValue[i] = new int[PROBLEM_DIMENSION];

			int[][] quo = new int[_numberOfIntegrationPoints][];
			for (int i = 0; i < quo.Length; i++)
				quo[i] = new int[PROBLEM_DIMENSION];

			int[][] errors = new int[_numberOfIntegrationPoints][];
			for (int i = 0; i < errors.Length; i++)
				errors[i] = new int[PROBLEM_DIMENSION];

			int[] radix = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 };
			int[] spin = { 1, 2, 1, 5, 3, 8, 2, 7, 18, 11, 18, 3, 17, 24, 40, 15, 40, 49, 12, 30, 40, 70, 9, 39, 82 };
			double[] radish = { 2d, 3d, 5d, 7d, 11d, 13d, 17d, 19d, 23d, 29d, 31d, 37d, 41d, 43d, 47d, 53d, 59d, 61d, 67d, 71d, 73d, 79d, 83d, 89d, 97d };

			for (int kount = 0; kount < _numberOfIntegrationPoints; kount++)
				for (int i = 0; i < PROBLEM_DIMENSION; i++)
				{
					radishValue[kount][i] = radish[i];
					spinValue[kount][i] = spin[i];
					radixValue[kount][i] = radix[i];
					quo[kount][i] = kount;
				}

			int sumErrors = 0;
			do
			{
				sumErrors = 0;
				for (int j = 0; j < errors.Length; j++)
				{
					int[] errorsColj = errors[j];
					int[] quoColj = quo[j];
					for (int i = 0; i < errorsColj.Length; i++)
					{
						if (quoColj[i] != 0)
						{
							top[j][i] = top[j][i] * radishValue[j][i] + (quo[j][i] * spinValue[j][i] % radixValue[j][i]);
							bottom[j][i] *= radishValue[j][i];
							quoColj[i] /= radixValue[j][i];
							errorsColj[i] = 1;
						}
						else
							errorsColj[i] = 0;

						sumErrors += errorsColj[i];
					}
				}
			} while (sumErrors > 0);

			for (int j = 0; j < bottom.Length; j++)
			{
				double[] topColj = top[j];
				double[] bottomColj = bottom[j];
				double[] integrationPointsColj = _integrationPoints[j];

				for (int i = 0; i < topColj.Length; i++)
					integrationPointsColj[i] =
						topColj[i] /
						bottomColj[i];
			}
		}
	}
}

Bin neugierig ob das jemand nach C++ übersetzt.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Die C#-Variante kann sich ja jeder bei Interesse selst kompiliren. Die Fortran-Varianten Matmul.exe (CRC32: F3F4A745) und MonteCarlo.exe (49D0FB7E) sind als Anhang zu groß (wegen der Optimierung: Loop unrolling und inling) können aber von meinem SkyDrive heruntergeladen werden.

Zur Fortran-Version ist noch anzumerken dass die Standardoptimierung verwendet wurde. Durch Fine-Tuning der Parameter ließe sich da sicher noch was rausholen. Aber was solls: Der alte Großmeister hat den jungen Alleskönner um Ecken geschlagen 😁

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

U
1.688 Beiträge seit 2007
vor 14 Jahren

Hallo,

rein interessehalber: wie schneidet eigentlich der Fortran-Compiler der Gnu Compiler Collection gegenüber Lahey-Fortran ab? Kommt der annähernd ran (rein qualitativ, unabhängig von dem konkreten Problem)?

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Hallo,

ist zwar schon eine Zeit lang her aber ich soweit ich mich erinnern kann übersetzt der Gnu Fortran Compiler denn Quellcode zuerst nach C und kompiliert diesen dann. Qualitativ hab ich beide nie wirklich verglichen weil für mich der G95 (wegen des Zwischenschrittes zu C) kein Fortran-Compiler ist. Vom Bedienkomfort des Lahey-Werkzeugs ganz zu schweigen (VS-Integration, sogar mit Intellisense 😉).

Der obige Vergleich wurde mit Gnu 95 Fortran Compiler - Versionsinfo: G95 (GCC 4.0.3 (g95 0.90!) Jul 27 2006) - wiederholt. Ergebnis angehängt.
Daraus ist ersichtlich dass der Lahey Compiler wirklich schnell ist und sein Geld auch Wert ist. C# schneidet im Vergleich zum Gnu Compiler gar nicht mehr so schlecht ab und ist bei der Monte Carlo Integration dem sogar überlegen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

U
1.688 Beiträge seit 2007
vor 14 Jahren

Hallo,

ist zwar schon eine Zeit lang her aber ich soweit ich mich erinnern kann übersetzt der Gnu Fortran Compiler denn Quellcode zuerst nach C und kompiliert diesen dann.

Soweit ich weiß, trifft das auf die aktuellen gfortran und g95 nicht zu.

M
1.439 Beiträge seit 2005
vor 14 Jahren

@gfoidl

Dein Benchmark ist nicht aussagekräftig, denn du vergleichst Äpfel mit Birnen.
Auf der einen Seite verwendest du den sicher hochoptimierten matmul-Befehl von Fortran und auf der anderen Seite eine naive C# Implementierung.

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Dein Benchmark ist nicht aussagekräftig, denn du vergleichst Äpfel mit Birnen.
Auf der einen Seite verwendest du den sicher hochoptimierten matmul-Befehl von Fortran und auf der anderen Seite eine naive C# Implementierung.

Das sehe ich nicht so. Es wird die Sprache Fortran mit der Sprache C#/.net verglichen. Warum sollte also der Befehl matmul, der Sprachbestandteil von Fortran ist, nicht im Vergleich verwendet werden dürfen? 🤔 Das wäre ja fast so als ob bestimmte Datentypen nicht verwendet werden dürfen weil es in einer anderen Sprache kein Äquivalent gibt.

eine naive C# Implementierung.

Naiv würde ich auch nicht unbedingt sagen denn diese Variante ist (wahrscheinlich) die schnellste Matrix-Multiplikation die mit C# machbar ist (Parallelisierung mal außen vorgelassen).

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

1.361 Beiträge seit 2007
vor 14 Jahren

Hi gfoidl,

was mir spontan als Speed-Up einfallen würde ist schonmal die Umkehrun der Array-Lauf-Richtung.


for (int k = 0; k < a.Length; k++)
// wird zu
for (int k = a.Length-1; k >=0 ; k--)

Spart immer eine Instruktion (weil Vergleich mit der 0 ein Befehl ist, während Vergleich mit andere Zahl immer zwei benötigt)

Das macht der C#-Compiler meines Wissens nicht (Der C/C++-Compiler im VS allerdings schon)

Desweiteren könnte man anstatt der "naiven" Implementierung auch den Algorithmus von Strassen verwenden. Das sollte bei 1000x1000 Matrizen auch schneller sein.

Wie die Fortran-Methode intern arbeitet wissen wir ja nicht.

beste Grüße
zommi

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Hab in Fortran für die Matrix-Multiplikaton mal den Befehl durch Schleifen (j,i,k) ersetzt. Die Zeit ist immer noch in der selben Größenordnung.

Desweiteren könnte man anstatt der "naiven" Implementierung auch den Algorithmus von Strassen verwenden.

Der Strassen-Algorithmus ist mir sehr wohl bekannt. Der (kleine) Vorteil von O(n2,8 ) gegenüber O(n3) kann in C# durch Verwendung von Parallelisierung mehr als wettgemacht werden. Daher verwende ich den Strassen in der Praxis nicht (mehr).

was mir spontan als Speed-Up einfallen würde ist schonmal die Umkehrun der Array-Lauf-Richtung.

Das ist eine gute Idee die ich sicher testen werden. Allerdings hat der von dir gepostete Code einen Fehler in Hinsicht Optimierung der das alles wieder zunichte machen würde.

for (int k = a.Length-1; k >=0 ; k--)

Durch a.Length -1 führt kann keine Range-Check-Elimination vom JITer durchgeführt werden. Daher muss für jeden Zugriff die Indexgrenzen geprüft werden und ist somit langsamer.

Spart immer eine Instruktion (weil Vergleich mit der 0 ein Befehl ist, während Vergleich mit andere Zahl immer zwei benötigt)
Das macht der C#-Compiler meines Wissens nicht

Auch das werde ich herausfinden 😉

Bis später.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

was mir spontan als Speed-Up einfallen würde ist schonmal die Umkehrun der Array-Lauf-Richtung.

Es war leider nur die Idee gut. Durch das Fehlen der Range-Check-Elimination steigt die Laufzeit der Schleifen wie im folgenden Beispielcode auf ~2x.


using System;
using System.Diagnostics;

namespace Array___Schleifenrichtung
{
	class Program
	{
		static void Main(string[] args)
		{
			const int N = 100000;
			int[] array = new int[N];
			Stopwatch sw = new Stopwatch();

			// JITen:
			AccessForward(array);
			AccessBackward(array);

			// Test:
			sw.Reset();
			sw.Start();
			AccessForward(array);
			sw.Stop();
			Console.WriteLine(sw.ElapsedTicks);

			sw.Reset();
			sw.Start();
			AccessBackward(array);
			sw.Stop();
			Console.WriteLine(sw.ElapsedTicks);
		}
		//---------------------------------------------------------------------
		static void AccessForward(int[] array)
		{
			// Array ist für die Methode auf dem lokalem Stack daher
			// kann Range-Check-Elimination durchgeführt werden.
			for (int i = 0; i < array.Length; i++)
				array[i] = i;
		}
		//---------------------------------------------------------------------
		static void AccessBackward(int[] array)
		{
			// Array ist für die Methode auf dem lokalem Stack daher
			// kann Range-Check-Elimination durchgeführt werden wenn da
			// nicht array.Length - 1 stünde ;)
			for (int i = array.Length -1; i >= 0; i--)
				array[i] = i;
		}
	}
}

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Vorwärts- / Rückwertsschleife

Spart immer eine Instruktion (weil Vergleich mit der 0 ein Befehl ist, während Vergleich mit andere Zahl immer zwei benötigt)

Das macht der C#-Compiler meines Wissens nicht (Der C/C++-Compiler im VS allerdings schon)

Mit dieser Aussgage hast du recht.
Hier der Vergleich des geJITeten Codes (Auszug):


Vorwärts-Schleife (leerer Rumpf):

00000000  push        ebp
00000001  mov         ebp,esp
00000003  xor         eax,eax                   ; Zähler auf 0 setzen
00000005  test        ecx,ecx                   ; Flags des Schleifenende bitweise ANDen
00000007  jle         0000000E                  ; Wenn test <= 0 ans Ende der Methode springen
00000009  inc         eax                       ; Zähler inkrementieren
0000000a  cmp         eax,ecx                   ; Zähler mit dem Schleifenende vergleichen
0000000c  jl          00000009                  ; Wenn Zähler kleiner Schleifenende -> weitere Iteration
0000000e  pop         ebp                       ; Rücksprung aus der Methode


Rückwärts-Schleife

00000000  push        ebp
00000001  mov         ebp,esp
00000003  dec         ecx                       ; n - 1 durchführen
00000004  mov         eax,ecx                   ; n - 1 der Zählvariable zuweisen
00000006  test        eax,eax                   ; Flags des Zählers bitweise ANDen
00000008  jl          0000000D                  ; Wenn test? < 0 ans Ende der Methode springen
0000000a  dec         eax                       ; Zähler dekrementieren
0000000b  jns         0000000A                  ; Weitere Iteration wenn Zähler >= 0 (no sign).
0000000d  pop         ebp                       ; Rücksprung aus der Methode

Für die Forwärtsschleife werden 3 Befehle benötigt:1.Zähler inkrementieren 1.Zähler mit dem Schleifenende vergleichen 1.Wenn der Zähler < Schleifenende ist Sprung zum Beginn des Schleifenrumpfs (jump less)

Bei der Rückwärtsschleife werden 2 Befehle benötigt:1.Zähler dekrementieren 1.Wenn der Zähler positiv Sprung zum Beginn des Schleifenrumpfs (jump no sign)

Range-Check-Elimination (RCE)


[MethodImpl(MethodImplOptions.NoInlining)]
static void ForwardLoop(int[]  arr)
{
	for (int i = 0; i < arr.Length; i++)
		arr[i] = i;
}

[MethodImpl(MethodImplOptions.NoInlining)]
static void BackwardLoop(int[] arr)
{
	for (int i = arr.Length - 1; i >= 0; i--)
		arr[i] = i;
}

Ergibt als Maschinencode:


Vorwärtsschleife -> Range-Check-Elimination wird angewandt

00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  xor         edx,edx 
00000005  mov         eax,dword ptr [ecx+4] 
00000008  test        eax,eax 
0000000a  jle         00000015 
0000000c  mov         dword ptr [ecx+edx*4+8],edx 
00000010  inc         edx  
00000011  cmp         eax,edx 
00000013  jg          0000000C 
00000015  pop         ebp  
00000016  ret              


Vorwärtsschleife -> keine Range-Check-Elimination

00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        esi  
00000004  mov         esi,dword ptr [ecx+4] 
00000007  xor         eax,eax 
00000009  test        esi,esi 
0000000b  jle         0000001D 
0000000d  mov         edx,dword ptr [ecx+4] 
00000010  cmp         eax,edx 
00000012  jae         00000020 
00000014  mov         dword ptr [ecx+eax*4+8],eax 
00000018  inc         eax  
00000019  cmp         eax,esi 
0000001b  jl          00000010 
0000001d  pop         esi  
0000001e  pop         ebp  
0000001f  ret              
00000020  call        792AC2B4 
00000025  int         3    

D.h. 12 Befehle bei RCE gegenüber 19 Befehlen mit RCE. Bei großen Schleifen (viele Iterationen) macht das sehr wohl bemerkbar.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

1.361 Beiträge seit 2007
vor 14 Jahren

Hi gfoidl,

mhh...bei mir kommt komplett anderer ASM-Code raus:


        [MethodImpl(MethodImplOptions.NoInlining)]
        static void ForwardLoop(int[] arr)
        {
            for (int i = 0; i < arr.Length; i++)
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  sub         esp,8 
00000006  mov         dword ptr [ebp-8],ecx 
00000009  cmp         dword ptr ds:[00139244h],0 
00000010  je          00000017 
00000012  call        64379D79 
00000017  xor         edx,edx 
00000019  mov         dword ptr [ebp-4],edx 
0000001c  xor         edx,edx 
0000001e  mov         dword ptr [ebp-4],edx 
00000021  nop              
00000022  jmp         0000003E 
                arr[i] = i;
00000024  mov         eax,dword ptr [ebp-4] 
00000027  mov         edx,dword ptr [ebp-8] 
0000002a  cmp         eax,dword ptr [edx+4] 
0000002d  jb          00000034 
0000002f  call        6437B9B4 
00000034  mov         ecx,dword ptr [ebp-4] 
00000037  mov         dword ptr [edx+eax*4+8],ecx 
            for (int i = 0; i < arr.Length; i++)
0000003b  inc         dword ptr [ebp-4] 
0000003e  mov         eax,dword ptr [ebp-4] 
00000041  mov         edx,dword ptr [ebp-8] 
00000044  cmp         eax,dword ptr [edx+4] 
00000047  jl          00000024 
        }
00000049  nop              
0000004a  mov         esp,ebp 
0000004c  pop         ebp  
0000004d  ret   

viel länger als deiner. Der Laufindex i wird auch stets auf dem Stack gespeichert. Warum nur? Wieso kompiliert deiner besseren Code? Womit kompilierst du?
Ich hab VisualStudio 2008 (SP1) und .Net 3.5 SP1.

beste Grüße
zommi

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Hi zommi,

Der Laufindex i wird auch stets auf dem Stack gespeichert. Warum nur? Wieso kompiliert deiner besseren Code? Womit kompilierst du?

Bist du sicher dass dein Maschinencode nicht mit angehängtem Debugger erstellt wird? Wäre dies der Fall so werden vom JITer keine Optimierungen durchgeführt.

Im CLR-Debugger muss bei den Optionen unter Debuggen*Nur meinen Code aktivieren (nur verwaltet) - deaktiviert sein *JIT-Optimierung beim Laden von Module unterdrücken (nur verwaltet) - deaktiviert sein

Womit kompilierst du? Ich hab VisualStudio 2008 (SP1) und .Net 3.5 SP1.

VS 2008 - also der csc.exe der bei .net 3.5 SP1 dabei ist - 32 bit.

Edit: Und natürlich in der Releas-Konfiguration 😉

Edit: Wenn ich den die obigen Optionen aktiviere bekomme ich den selben Code wie du. D.h. dein Code ist deshalb länger weil der JITer keine Optimierungen durchgeführt hat (wegen Debugger).

mfG Gü

Speedski, Assembler, Disassembler, Maschinencode

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

1.361 Beiträge seit 2007
vor 14 Jahren

Cool
danke dir 😃

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Hallo,

nur der Vollständigkeithalber: Siehe auch C# gegen den rest. Wer ist schneller?

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
401 Beiträge seit 2008
vor 14 Jahren

Bin neugierig ob das jemand nach C++ übersetzt.

Servus,

zur Belebung des Thread nehme ich die Einladung an 😁

ich habe deinen C#-Code genommen und mehr oder wenig in C++ übersetzt. Aus Faulheit habe ich kein 100% C++ (siehe Cast, Array, ...) verwendet. Zudem spendierte ich dem Beispiel einen Memory-Pool. Dieser macht dem Programmierer das Leben einfacher, da dieser eventuelle Speicherleaks verhindert 😁

Ich habe auf keinerlei Optimierungsmaßnahmen, sowie eventuelle Verschlechterungen, geachtet (range check, Schleife Vor- oder Rückwärts, ...). Einfach abgetippt eben. Hoffentlich ohne Fehler. Abschreiben war noch nie meine Stärke 😉

Bei mir (Fedora + KDE) benötigt das Programm etwa 7,1MB RAM und eine CPU-Zeit (effektiv Zeit, nicht verstrichene Zeit) von ??? Microsekunden. Jedenfalls lange. Vielleicht ein Schleifen-Fehler? Oder ist eine Ausführungszeit von > 30 min normal?

Achaj, die Code-Stücke sind alles andere als schön und stellt ein schlechtes C++ Beispiel dar. Ich wollte so wenig Hilfsklassen, Struct und Makros verwenden und das ist dabei raus gekommen.

Installation:

  • die drei Codestücke in einem separaten Ordner platzieren
  • compilieren
  • starten

Edit: Code entfernt (siehe weiter unten)

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Servus,

Vielleicht ein Schleifen-Fehler? Oder ist eine Ausführungszeit von > 30 min normal?

Eher ein Fehler. Die Fortran-Programme brauchen ca. 1 Sekunde 😉

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
401 Beiträge seit 2008
vor 14 Jahren

Eher ein Fehler. Die Fortran-Programme brauchen ca. 1 Sekunde 😉

Hab den Code nochmal überflogen und einen Fehler gefunden. Dieser war in der gleich() Methode 🙂 Den Pool hab ich entfernt und auf malloc / free umgestellt. Also mit Board-Mitteln gelösst. Du findest ihn im oberen Post.

Bei mir dauert der Test (GCC) ~2,0 s. Kannst du die C++ Implementation bei dir mit VC und dem ICC (Intel-Compiler) testen? Letzterer ist für nicht komerzielle Projekte kostenlos.

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Kannst du die C++ Implementation bei dir mit VC und dem ICC (Intel-Compiler) testen?

Könnte ich schon, aber das kannst du ja auch 😁
Wenn es um den Vergleich mit Fortran geht ist weiter oben der Link zu den Programmen.

mfG Gü

Edit: Link hinzugefügt

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
401 Beiträge seit 2008
vor 14 Jahren

Dann werd ich wohl meine alte XP CD suchen gehen. Werds höchstwahrscheinlich morgen aufsetzen und einen Vergleich druchführen.
Denn ICC gibts anscheinend nur für Linux zum kostenlosen Download 👍 Dafür ist ein Fortran-Compiler mit dabei 🙂

Wie gut kennst du dich mit dem VC und der Express-Version aus? Ist der erstellte Code 100% gleichwertig?

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Wie gut kennst du dich mit dem VC und der Express-Version aus? Ist der erstellte Code 100% gleichwertig?

Mit C/C++ gar nicht. Aber bei C# ist der Kompiler Bestandteil des Frameworks und somit unabhängig von der Visual Studio-Version => der Code bzw. das Kompilat ist ident.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
401 Beiträge seit 2008
vor 14 Jahren

@gfoidl Wieso multiplizierst du die vergangenen Millisekunden mit 0.2?
in Run()

sw.Stop();
_milliSeconds = (long)(sw.ElapsedMilliseconds * 0.2);

Da wäre C# und Mono deutlich schneller als C++ 🤔

Ich kenne mich in Fortran nicht aus, aber teilst du hier die Zeit durch 5?

Leider unterstützt der Intel Compiler kein SELinux 🙁 Einen Eingriff in mein privates System möchte ich nicht vornehmen und verzichte auf einen direkt Vergleich.

System: (die Angaben müssten ausreichen)

  • Intel DualCore (4 Kerne) @2,84 GHz
  • 8 GB RAM
  • Motherboard & GraKa von MSI
  • ...

Der Vergleich von Fortran, C#, C++ und Java verlief wie erwartet. Allerdings überraschte mich der GCC. Im direkten Vergleich mit dem VC (Visual Studio Prof. 2008 ) konnte $MS$ den freien Compiler nie das Wasser reichen. Zuvor hätte ich den VC weiter vorne eingeschätzt. Microsoft arbeitet mit Intel eng zusammen, was einen enormen Vorteil bedeutet. Hmm egal.

Als leidenschaftlicher Java-Fan habe ich die C#-Implementierung nach Java portiert und die Range-Check-Elimination entfernt. Ohne weitere Optimierungen zeigen sich die stärken von Java (aktuelle Version: 1.6 SE).

Der Code von allen könnte natürlich noch optimiert werden, aber das ist nicht der Sinn hier. Falls mir der ein oder andere Fehler unterlaufen ist, sagt bitte was. Niemand ist unfehlbar 🙂

Sofern gfoidl nichts dagegen hat, stelle ich den Code unter dieser Adresse zum kostenlosen Download zur Verfügung. Über einen Bericht des ICC (Intel Compiler) im Vergleich des GCC würde ich mich sehr freuen. Der ICC ist für Linux + nicht kommerzielles Projekt für C++ und Fortran + Bibliotheken kostenlos zu downloaden.
Anmerkung: Der VC definiert bereits einen Type _int32, weswegen die typedef auskommentiert werden müssen.

Gruß,
Thomas

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Wieso multiplizierst du die vergangenen Millisekunden mit 0.2?

Weil ich 5 Messungen durchführe und dann den arithmetischen Mittelwert berechne. Eine Einzelmessung ist statistisch nicht vertretbar. Es könnte auch / 5 dastehen (aber ich hab mir irgendwie angewöhnt Divisionen durch eine Konstante durch eine Multiplikationen zu erstetzen, der C#- und der JIT-Kompiler erledigen dies nicht).

Ich kenne mich in Fortran nicht aus, aber teilst du hier die Zeit durch 5?

Bei der Matritzenmultiplikation ja, bei der Monte-Carlo-Integration werde eh mehrere Durchläufe ausgeführt und die Gesamtzeit ausgeben (das ist genauso vergleichbar wie das arithmetische Mittel).

Microsoft arbeitet mit Intel eng zusammen, was einen enormen Vorteil bedeutet.

Da GNU OpenSource ist wird dafür der Kompiler von div. Fachkräften weiterentwickelt. Deshalb sind die Produkte auch nicht schlecht 😉

Ich kenn mich mit JAVA und C++ nicht aus aber die Vergleich sind interessant. Es wird 5x gemessen und die Gesamtzeit verwendet - irgendwas passt da nicht. Es kann jedoch sein dass ich was übersehen habe (da ich mich da nicht auskenne).
Weiters sollte auch die C++-Version mit der Standardoptimierung (wie Fortran) kompiliert werden. Oder wir kompilieren alles mit maximaler Optimierung 😉

Sofern gfoidl nichts dagegen hat, stelle ich den Code unter dieser Adresse zum kostenlosen Download zur Verfügung. Finde ich nicht nötig denn die gesamte Information ist hier bei myCSharp "erhältlich" - ein Link auf dieses Thema genügt. Zum anderen ist er schon zum Download angeboten und es ist keinerlei Hinweis auf den Urheber/die Quelle ersichtlich. Daher bitte den Download wieder entfernen - zumindest der Teile die ich begesteuert habe.
Hab mir jetzt deinen Blog-Beitrag durchgelesen und das hat mich umgestimmt 😉 Bitte dennoch folgeden Punkte ergänzen:*Verlinke hier in deinem Beitrag zu deinem Blog-Artikel. *Ersetze im Blog-Beitrag den Namen 'gfoidl' durch 'Günther Foidl'. *Füge in den Codes die ich beigesteuert habe bitte meinen Namen (Günther Foidl) und als Quelle myCSharp.de hinzu (eventuell auch ein Link zu diesem Thema).

BTW: Könntest du deinen geposteten C++-Code etwas schrumpfen (zumindest die leeren Code-Teile) damit das Thema etwas leichter zu lesen ist)?

Intel DualCore (4 Kerne)

Gibt es DualCore auch mit 4 Kernen 😉

Edit: Info wegen Veröffentlichung geändert.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
401 Beiträge seit 2008
vor 14 Jahren

Ich kenn mich mit JAVA und C++ nicht aus aber die Vergleich sind interessant. Es wird 5x gemessen und die Gesamtzeit verwendet - irgendwas passt da nicht. Es kann jedoch sein dass ich was übersehen habe (da ich mich da nicht auskenne).
Weiters sollte auch die C++-Version mit der Standardoptimierung (wie Fortran) kompiliert werden. Oder wir kompilieren alles mit maximaler Optimierung 😉

Ich lasse bei Java und der C++-Variante die for-Schleife durchlaufen und nehme davor und zum Schluß die Zeit. Diese wird ausgegeben. Für das Ergebniss spielt es keine Rolle, ob die Zeit im Gesamten oder auf einen Durchlauf betrachtet wird. Daher spare ich mir das und ging davon aus, dass du ebenso vorgegangen bist. Am Schluss war ich dann etwas verwirrt, aber trotzdem die richtige Schlüsse daraus gezogen.

Die Optimierungseinstellungen des GCC machen in der Zeit fast keinen Unterschied. So gut wie jedes Release wird mit -o1, -o2, oder -o3 kompiliert. Lediglich -o0 verlangsamt den Test um den Faktor 1,7. Dafür ist den Benchmark zu klein. Aber diese Einstellung wird nur zum Debuggen benutzt.
Der C++-Code lässt sich auch noch verfeinern, stellt somit nicht das Maximum dar.

Java ist von Syntax her ähnlich wie C#. Die Annotations beginnen mit @...(), C# [...()]. Es gibt nur ein final, kein const oder readonly. "final" lässt sich zum Teil mit Reflection umgehen.

Hier noch der Blog-Eintrag, den Günther Foidl angesprochen hat.

Gruß,
Thomas

gfoidl Themenstarter:in
6.911 Beiträge seit 2009
vor 14 Jahren

Sehr schöne Zusammenfassung auf dem Blog 👍 (und sogar meine Namen verlinkt 😉)

Java ist von Syntax her ähnlich wie C#.

Soviel hab ich schon kapiert 👅

Noch ein kleiner Tipp:
Die 3D-Diagramme schauen schön aus sind aber vom "Informationsgehalt" nicht so gut wie die (klassischen) 2D-Diagramme. Da die Information auf einem 2D-Medium (Papier, Bildschirm, ...) wiedergeben wird ist das einfach so. In wissenschaftlichen Publikationen finden sich deshalb auch hauptsächlich 2D-Diagramme (für Darstellungen solcher Art wie wir es hier haben). Dort wo die grafische Aufmachung wichtiger ist als die Information schauen die Verhältnisse anders aus 😉

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
401 Beiträge seit 2008
vor 14 Jahren

Java ist von Syntax her ähnlich wie C#.
Soviel hab ich schon kapiert 👅

Noch ein kleiner Tipp:
Die 3D-Diagramme schauen schön aus sind aber vom "Informationsgehalt" nicht so gut wie die (klassischen) 2D-Diagramme. Da die Information auf einem 2D-Medium (Papier, Bildschirm, ...) wiedergeben wird ist das einfach so.){gray}

Genau das wollte ich mit dem 3D-Diagramm verhindern. Ich stimme dir 100% zu und setze diese 3D-Dinger so gut wie nie ein. Ich möchte die Verhältnisse PI mal Daumen darstellen und nicht das exakte Ergebniss. Aber danke für den Tipp.

G
45 Beiträge seit 2010
vor 13 Jahren

Der Thread ist wirklich sehr interessant. Ich habe mich auch mit der Thematik beschäftigt, und ich werde letztlich die Vorteile beider Sprachen verwenden, indem ich zeitkritische Berechnungen auslagere und damit Fortran-DLLs "beauftrage".

Erste Tests haben gezeigt, dass die Performancevorteile sehr groß sind. Nachteil: Man muss einen teuren Compiler kaufen...