|
| » myCSharp.de Diskussionsforum |
|
|
|
|
Autor
 |
|
pdelvo
myCSharp.de-Mitglied

Dabei seit: 02.11.2008
Beiträge: 991
Entwicklungsumgebung: Visual Studio Team System 2010
 |
|
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.
Gruß pdelvo
|
|
06.09.2009 13:46 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
winSharp93
myCSharp.de-Team (Moderation)

Dabei seit: 19.01.2007
Beiträge: 3.870
Entwicklungsumgebung: VS 2010 Professional Herkunft: Freiburg
 |
|
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.
|
|
06.09.2009 15:35 |
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
Themenstarter
 |
|
| Zitat: |
| 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!).
| Zitat: |
| 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.
| Zitat: |
| 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ü
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von gfoidl am 06.09.2009 17:51.
|
|
06.09.2009 16:17 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
winSharp93
myCSharp.de-Team (Moderation)

Dabei seit: 19.01.2007
Beiträge: 3.870
Entwicklungsumgebung: VS 2010 Professional Herkunft: Freiburg
 |
|
| Zitat von gfoidl: |
| 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).
|
|
06.09.2009 17:02 |
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
Themenstarter
 |
|
| Zitat: |
| 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
| Zitat: |
| 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!
C#-Code (IBenchmark): |
namespace Benchmark
{
public interface IBenchmark
{
void Run();
long Milliseconds { get; }
string Name { get; }
}
}
|
C#-Code (Program): |
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.
C#-Code (Matmul): |
using System;
using System.Diagnostics;
using gfoidl.Parallelization;
namespace Benchmark
{
public class Matmul : IBenchmark
{
const int N = 1000;
private double[][] _a, _b, _c;
#region IBenchmark Member
public void Run()
{
CreateMatrices();
DoMatmul(_a, _b, _c);
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++)
{
double[] bColj = b[j];
double[] cColj = c[j];
for (int k = 0; k < a.Length; k++)
{
double[] aColk = a[k];
double b_kj = bColj[k];
for (int i = 0; i < aColk.Length; i++)
cColj[i] += aColk[i] * b_kj;
}
}
}
}
}
|
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):
Code (Fortran Matmul): |
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
|
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!
Code (Fortran Monte-Carlo-Integration): |
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:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
|
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?
C#-Code (Monte-Carlo-Integration): |
using System;
using System.Diagnostics;
namespace Benchmark
{
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()
{
DoMonteCarlo();
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][];
double[][] localIntegrationPoints = _integrationPoints;
for (int i = 0; i < localIntegrationPoints.Length; i++)
localIntegrationPoints[i] = new double[PROBLEM_DIMENSION];
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;
}
return errorOfIntegral;
}
private void Gleich()
{
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ü
gfoidl hat dieses Bild (verkleinerte Version) angehängt:
 Volle Bildgröße
|
|
06.09.2009 19:02 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
Themenstarter
 |
|
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ü
|
|
06.09.2009 19:09 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
ujr
myCSharp.de-Poweruser/ Experte
Dabei seit: 24.11.2007
Beiträge: 1.054
 |
|
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)?
|
|
07.09.2009 09:31 |
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
ujr
myCSharp.de-Poweruser/ Experte
Dabei seit: 24.11.2007
Beiträge: 1.054
 |
|
Hallo,
| Zitat von gfoidl: |
| 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.
|
|
07.09.2009 12:21 |
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
marsgk
myCSharp.de-Mitglied
Dabei seit: 04.06.2005
Beiträge: 1.400
Entwicklungsumgebung: Notepad++ + csc + nmake Herkunft: Linz, Austria
 |
|
@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.
|
|
07.09.2009 13:44 |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
Themenstarter
 |
|
| Zitat von marsgk: |
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.
| Zitat: |
| 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ü
|
|
07.09.2009 14:50 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
zommi
myCSharp.de-Poweruser/ Experte

Dabei seit: 14.11.2007
Beiträge: 1.010
Entwicklungsumgebung: VS 2005+2010 Herkunft: Berlin
 |
|
Hi gfoidl,
was mir spontan als Speed-Up einfallen würde ist schonmal die Umkehrun der Array-Lauf-Richtung.
C#-Code: |
for (int k = 0; k < a.Length; k++)
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
|
|
07.09.2009 15:03 |
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
Themenstarter
 |
|
| Zitat: |
| 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.
C#-Code: |
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();
AccessForward(array);
AccessBackward(array);
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)
{
for (int i = 0; i < array.Length; i++)
array[i] = i;
}
static void AccessBackward(int[] array)
{
for (int i = array.Length -1; i >= 0; i--)
array[i] = i;
}
}
}
|
mfG Gü
|
|
07.09.2009 15:29 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
Themenstarter
 |
|
Vorwärts- / Rückwertsschleife
| Zitat: |
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):
Code: |
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
|
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:- Zähler inkrementieren
- Zähler mit dem Schleifenende vergleichen
- Wenn der Zähler < Schleifenende ist Sprung zum Beginn des Schleifenrumpfs (jump less)
Bei der Rückwärtsschleife werden 2 Befehle benötigt:- Zähler dekrementieren
- Wenn der Zähler positiv Sprung zum Beginn des Schleifenrumpfs (jump no sign)
Range-Check-Elimination (RCE)
C#-Code: |
[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:
Code: |
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:
|
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ü
|
|
07.09.2009 16:46 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
zommi
myCSharp.de-Poweruser/ Experte

Dabei seit: 14.11.2007
Beiträge: 1.010
Entwicklungsumgebung: VS 2005+2010 Herkunft: Berlin
 |
|
Hi gfoidl,
mhh...bei mir kommt komplett anderer ASM-Code raus:
Code: |
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:
|
[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
|
|
07.09.2009 17:21 |
E-Mail |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
Siassei
myCSharp.de-Mitglied
Dabei seit: 15.02.2008
Beiträge: 316
 |
|
| Zitat von gfoidl: |
| 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)
Dieser Beitrag wurde 4 mal editiert, zum letzten Mal von Siassei am 10.10.2009 20:03.
|
|
22.09.2009 21:15 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
Themenstarter
 |
|
Servus,
| Zitat: |
| 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ü
|
|
22.09.2009 21:27 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
Siassei
myCSharp.de-Mitglied
Dabei seit: 15.02.2008
Beiträge: 316
 |
|
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?
|
|
09.10.2009 19:51 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
Themenstarter
 |
|
| Zitat: |
| 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ü
|
|
09.10.2009 20:05 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
Siassei
myCSharp.de-Mitglied
Dabei seit: 15.02.2008
Beiträge: 316
 |
|
@gfoidl Wieso multiplizierst du die vergangenen Millisekunden mit 0.2?
in Run()
C#-Code: |
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
Siassei hat dieses Bild (verkleinerte Version) angehängt:
 Volle Bildgröße
Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von Siassei am 10.10.2009 16:41.
|
|
10.10.2009 16:26 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
Siassei
myCSharp.de-Mitglied
Dabei seit: 15.02.2008
Beiträge: 316
 |
|
| Zitat von gfoidl: |
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
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von Siassei am 10.10.2009 21:20.
|
|
10.10.2009 20:34 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
Themenstarter
 |
|
Sehr schöne Zusammenfassung auf dem Blog
(und sogar meine Namen verlinkt
)
| Zitat: |
| 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ü
|
|
10.10.2009 20:48 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
Siassei
myCSharp.de-Mitglied
Dabei seit: 15.02.2008
Beiträge: 316
 |
|
[offtopic]
| Zitat von gfoidl: |
Sehr schöne Zusammenfassung auf dem Blog :O (und sogar meine Namen verlinkt ;))
| Zitat: |
| Java ist von Syntax her ähnlich wie C#. |
Soviel hab ich schon kapiert :tongue:
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. |
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.
[/offtopic]
|
|
10.10.2009 21:00 |
E-Mail |
Website |
Beiträge des Benutzers |
zu Buddylist hinzufügen
|
|
|
|