Bei codeproject gab es die Tage einen Artikel zum Performance-Vergleich
diverser Programmiersprachen.
http://www.codeproject.com/KB/tips/Performances.aspx
Denn Sinn des ganzen Unterfangens bei so einfachen Operationen mal außen
vor gelassen, würde mich mal interessieren wieso der GCC dort so
schlecht wegkommt.
Im Vergleich zu den anderen Compilern wird für die Berechnungsschleife
die 4-fache Zeit benötigt.
Ich habe mir mal die Assembler-Ausgabe des GCC (x86 mingw 3.4.5) der
Additions-Schleife mit Optimierung -O3 angesehen.
Mangels Verfügbarkeit der anderen Compiler würde ich gerne mal sehen wo
man da den Faktor 4 herausholen kann.
Es kann natürlich sein dass die anderen Compiler die Sinnlosigkeit der
Schleifen erkennen und diese komplett wegoptimieren.
Hier der Assemblercode von der Additionsschleife (im IDA
Assembler-Stil):
C
Bartli schrieb:>> x86 mingw 3.4.5>> Na da hast du ja einen ganz aktuellen gcc...
Liegt daran dass der in Zusammenhang mit wxDevC++ auf meinen Rechner
gekommen ist, und die sind ja immer 'etwas' hintendran.
Ob eine neuere Version bei so einem simpel-Beispiel aber so viel anders
machen kann?
Bernd schrieb:> GCC (x86 mingw 3.4.5)
Wo rennen denn noch solche Zombies rum :-) ?
Also da gibt es dramatische Veränderungen, oft zum Guten, für AVR auch
mal etwas kontraproduktiv.
Es gibt hier einige, die die gcc-Versionen sammeln bzw. parallel
vorhalten, um eben die Veränderungen sehen/erkennen zu können.
Hol' Dir doch einfach den aktuellsten und poste mal das Resultat.
Ok, hab mir mal einen neuen GCC installiert, Version 4.6.1 ebenfalls mit
-O3 und -ansi übersetzt.
C-Programm:
1
intmain()
2
{
3
volatileintA[100000];
4
volatileintB[100000];
5
volatileintC[100000];
6
inti,j;
7
8
for(i=0;i<100000;i++){
9
A[i]=rand()+1;
10
B[i]=rand()+1;
11
}
12
13
for(j=0;j<10000;j++){
14
for(i=0;i<100000;i++){
15
C[i]=A[i]+B[i];
16
}
17
}
18
return0;
19
}
Ohne volatile schmeißt der GCC 4.6.1 die zweite Schleife komplett raus.
Die erste bleibt aber kurioserweise erhalten.
Der Assemblercode der zweiten Schleife:
1
mov ebx, 2710h
2
loc_401BDC:
3
xor eax, eax
4
db 66h
5
nop
6
loc_401BE0:
7
mov ecx, [esp+eax*4+4+var_A]
8
mov edx, [esp+eax*4+4+var_B]
9
add edx, ecx
10
mov [esp+eax*4+4+var_C], edx
11
inc eax
12
cmp eax, 186A0h
13
jnz short loc_401BE0
14
dec ebx
15
jnz short loc_401BDC
Bis auf dass die Schleife andersherum durchlaufen wird, scheint es von
den Befehlen her nicht unbedingt schneller zu sein.
Entweder die Intel-Compiler haben da eine geheime Magie, oder mit dem
Test bei codeproject stimmt gewaltig was nicht.
Bernd schrieb:> Ohne volatile schmeißt der GCC 4.6.1 die zweite Schleife komplett raus.
Weil du die dynamischen Arrays durch lokale ersetzt hast.
> Die erste bleibt aber kurioserweise erhalten.
Weil darin rand() aufgerufen wird, und der Aufruf kann nicht
wegoptimiert werden.
Schleifen wie die sind für SIMD-Instruktionen perfekt. Möglicherweise
nutzt der Intel-Compiler die. Wenn man dem gcc z.B. noch -march=core2
angibt, sieht der Code ganz anders aus.
Rolf Magnus schrieb:> Schleifen wie die sind für SIMD-Instruktionen perfekt. Möglicherweise> nutzt der Intel-Compiler die. Wenn man dem gcc z.B. noch -march=core2> angibt, sieht der Code ganz anders aus.
Hh, gibt bei mir momentan noch keinen großen Unterschied.
test mit -O3 -march=core2 -mtune=core2:
1
mov ebx, 2710h
2
lea esi, [esi+0]
3
loc_401BF0:
4
xor eax, eax
5
loc_401BF2:
6
mov ecx, [esp+eax*4+4+var_A]
7
mov edx, [esp+eax*4+4+var_B]
8
add edx, ecx
9
mov [esp+eax*4+4+var_C], edx
10
add eax, 1
11
cmp eax, 186A0h
12
jnz short loc_401BF2
13
sub ebx, 1
14
jnz short loc_401BF0
Ich habe den GCC jedoch in einer VM installiert, da ist der core2 ja
nicht der native Prozessor. Aber wenn ich es explizit angebe sollte er
es doch für diesen Prozessortyp optimieren können, oder?
Kann man denn davon ausgehen, dass die Laufzeitumgebungen der anderen
Sprachen (C#, Java) automatisch auf den jeweiligen Prozessor optimieren?
Das könnte dann ja wirklich ein Pluspunkt für diese Sprachen sein, da
man bei den compilierenden Sprachen schon bei der Erstellung auf den
jeweiligen Prozessor optimieren muss.
Bernd schrieb:> Hh, gibt bei mir momentan noch keinen großen Unterschied.> test mit -O3 -march=core2 -mtune=core2:> mov ebx, 2710h> lea esi, [esi+0]> loc_401BF0:> xor eax, eax> loc_401BF2:> mov ecx, [esp+eax*4+4+var_A]> mov edx, [esp+eax*4+4+var_B]> add edx, ecx> mov [esp+eax*4+4+var_C], edx> add eax, 1> cmp eax, 186A0h> jnz short loc_401BF2> sub ebx, 1> jnz short loc_401BF0
Hmm, also mein gcc 4.5.2 macht da folgendes draus:
(AT&T-Syntax, da Linux):
1
movl 60(%esp), %eax
2
leal 400000(%ebx), %esi
3
addl $16, %eax
4
movl %ebx, 52(%esp)
5
movl %eax, 44(%esp)
6
movl $10000, 56(%esp)
7
leal 16(%ebx), %eax
8
movl %edi, 48(%esp)
9
movl %eax, 40(%esp)
10
movl 60(%esp), %ebx
11
leal 16(%edi), %eax
12
movl %eax, 36(%esp)
13
.p2align 4,,10
14
.p2align 3
15
.L3:
16
movl 52(%esp), %edi
17
cmpl 40(%esp), %ebx
18
movl %edi, 28(%esp)
19
seta 60(%esp)
20
movl %ebx, %ecx
21
cmpl %edi, 44(%esp)
22
movl 48(%esp), %edx
23
setb %al
24
orb 60(%esp), %al
25
je .L11
26
cmpl 36(%esp), %ebx
27
movl 44(%esp), %edi
28
seta 60(%esp)
29
cmpl %edi, %edx
30
seta %al
31
orb 60(%esp), %al
32
je .L11
33
movl 28(%esp), %eax
34
.p2align 4,,10
35
.p2align 3
36
.L12:
37
movdqu (%edx), %xmm1
38
movdqu (%eax), %xmm0
39
addl $16, %edx
40
paddd %xmm1, %xmm0
41
addl $16, %eax
42
movdqu %xmm0, (%ecx)
43
addl $16, %ecx
44
cmpl %esi, %eax
45
jne .L12
46
decl 56(%esp)
47
jne .L3
48
.L7:
Viel Setup-Code, aber die innere Schleife (ab .L12) erledigt sowohl das
Lesen und Schreiben des Speichers, als auch die Addition mit
SIMD-Befehlen, die immer vier Werte auf einmal bearbeiten können.
Zum Vergleich nur mit -O3:
1
movl $10000, %ecx
2
.p2align 4,,7
3
.p2align 3
4
.L3:
5
xorl %eax, %eax
6
.p2align 4,,7
7
.p2align 3
8
.L4:
9
movl (%esi,%eax,4), %edx
10
addl (%ebx,%eax,4), %edx
11
movl %edx, (%edi,%eax,4)
12
addl $1, %eax
13
cmpl $100000, %eax
14
jne .L4
15
subl $1, %ecx
16
jne .L3
> Ich habe den GCC jedoch in einer VM installiert, da ist der core2 ja> nicht der native Prozessor. Aber wenn ich es explizit angebe sollte er> es doch für diesen Prozessortyp optimieren können, oder?
Ja. Dem Assembler ist ja erstmal egal, was du selbst für einen Prozessor
hast.
> Kann man denn davon ausgehen, dass die Laufzeitumgebungen der anderen> Sprachen (C#, Java) automatisch auf den jeweiligen Prozessor optimieren?
Ich würde es vermuten.
> Das könnte dann ja wirklich ein Pluspunkt für diese Sprachen sein, da> man bei den compilierenden Sprachen schon bei der Erstellung auf den> jeweiligen Prozessor optimieren muss.
Deshalb werden z.B. bei Multimedia-Sachen die zeitkritischsten Routinen
oft in mehreren Versionen mit ins Programm aufgenommen, und es wird dann
anhand der Prozessor-Kennung und/oder kurzen Performance-Messungen zur
Programmlaufzeit entschieden, welche verwendet wird.
Interessante Sache. Und ich dachte mit dem gcc hat man beim AVR schon
genug zu kämpfen.
Mit entsprechenden Compileroptionen habe ich es bei mir aber auch noch
hinbekommen:
-march=core2 -O3 -ftree-vectorize -ftree-vectorizer-verbose=5 -msse
-msse2
Mit -ftree-vectorizer-verbose bekommt man auch angezeigt aus welchen
Gründen er Schleifen nicht optimiert.
Laut der gcc Seite:
http://gcc.gnu.org/projects/tree-ssa/vectorization.html
wird bei -O3 -ftree-vectorize automatisch angewendet. Ich denke bei mir
fehlte die Option -msse -msse2.
Demnach scheint der GCC also doch gar nicht so schlecht zu sein, man
muss ihm nur etwas unter die Arme greifen.
Bernd schrieb:> Demnach scheint der GCC also doch gar nicht so schlecht zu sein, man> muss ihm nur etwas unter die Arme greifen.
Naja, er erzeugt halt per Default erstmal Code, der auch auf einem 386er
noch läuft. Will man mehr, dann muß man es explizit angeben.
Ich finde es allerdings interessant, daß -O3 wohl je nach Betriebssystem
unterschiedlich arbeitet. Ich habe nur -O3 -march=core2 angegeben und
sonst nichts. Aber es ist nicht auszuschließen, daß Ubuntu hier an den
Default-Einstellungen geschraubt hat. Das tun die ja gerne.
Bernd schrieb:> Ok, hab mir mal einen neuen GCC installiert, Version 4.6.1 ebenfalls mit> -O3 und -ansi übersetzt.>> volatile int A[100000];> volatile int B[100000];> volatile int C[100000];
Was ist denn das für ein unsinniger Test?
Man will die Performance von Compiler(Optimierungen) testen, schliesst
aber via volatile praktisch alle Optimierungen aus.
Merke: Wenn man Benchmarks machen will, sollte man den Code benchmarken,
für den man sich interessiert und nicht irgendeinen Märchencode.
Wenn man sich zB für die Performance eines AVR-Compilers interessiert,
ist es ziemlich sinnfrei, für die Bewertung SPEC2000-Benchmarks für den
Compiler zu machen...
Johann L. schrieb:> Was ist denn das für ein unsinniger Test?>> Man will die Performance von Compiler(Optimierungen) testen, schliesst> aber via volatile praktisch alle Optimierungen aus.>> Merke: Wenn man Benchmarks machen will, sollte man den Code benchmarken,> für den man sich interessiert und nicht irgendeinen Märchencode.>> Wenn man sich zB für die Performance eines AVR-Compilers interessiert,> ist es ziemlich sinnfrei, für die Bewertung SPEC2000-Benchmarks für den> Compiler zu machen...
Das mit volatile war ein Test, steht doch auch oben. Denn wenn nach der
for-Schleife nichts mehr kommt, wird diese wegoptimiert.
Das volatile habe ich bei den späteren Tests wieder entfernt. Die
wirkliche Optimierung kam aber (zumindest bei dem Windows MinGW GCC)
erst mit der weiteren Übersetzungsoption -msse2.
Der grundsätzliche fragwürdige "Benchmark" ist auch nicht auf meinem
Mist gewachsen, siehe codeproject Link im ersten Beitrag. Man müsste
dort nachfragen was der Autor sich von diesem erhofft hat. Mir ist nur
aufgefallen dass er den GCC explizit als schlecht darstellt. Und diesen
nutze ich eben für meine privaten Spielereien ausschließlich - da muss
man doch mal nachforschen.
Du müsstest in erster Linie mal verstehen, was passiert.
Natürlich kann man den gcc benchmarken. Es gibt zehnzeilige
C++-Quelltexte, die mehrere Stunden kompilieren, andere Compiler
erledigen das vielleicht schneller.
Man kann auch das nackte Kompilat benchmarken. Etwa im Hinblick darauf,
wie effizient es ist und wie gut der Compiler optimiert hat.
Schließlich lässt sich auch die ganze Toolchain benchmarken, wobei dann
auch profilorientierte Optimierungen möglich sind.
Man kann auch Äpfel und Birnen miteinander vergleichen, etwa wenn man
native Compiler mit JIT vergleicht. Der Java-Compiler wird kaum auf
irgendeinen Prozessor optimieren. Die ganzen .NET-Compiler vermutlich
auch nicht, das wäre ziemlich sinnfrei wo doch der Sinn von Java und
.NET ein gewisser Grad an portabilität sein soll. Da ist vielmehr
interessant, was die Laufzeitumgebung, also etwa die Java-VM, auf der
jeweiligen Plattform anstellt.
GCC ist mit C++ aber tatsächlich nicht der schnellste, es hilft meist,
vorkompilierte Header zu benutzen. Dafür hat(te) der GCC aber auch
längere Zeit den Ruf, quasi perfekten Code zu erzeugen. Sagt(e) man, wer
weiß, ob was dran ist oder war.
Intel bewirbt diesen Compilere ja gerade wegen der super Optimierungen
durch SIMD und Multiprocessing. Schon mal auf der Website von Intel
geguckt was der so kann ?
Dussel schrieb:> Standard C++? Hast du da ein Beispiel? Würde mich mal interessieren.
da eine keine zeilenlänge begrenzung in C gibt, kann das ein kompletten
openoffice sein.
Dussel schrieb:>>Es gibt zehnzeilige C++-Quelltexte, die mehrere Stunden kompilieren> Standard C++? Hast du da ein Beispiel? Würde mich mal interessieren.
Wohl 10 Zeilen in einem .cpp File und ein paar "etwas grössere"
Include-Files mit Stapel über Stapel aus Templates.
Peter II schrieb:> Dussel schrieb:>> Standard C++? Hast du da ein Beispiel? Würde mich mal interessieren.>> da eine keine zeilenlänge begrenzung in C gibt, kann das ein kompletten> openoffice sein.
Aber nicht, wenn du mehr als 9 #include brauchst :-)
dann nehmt einen richtigen compiler
cl test.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08
for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
dauer <1sec und ferig.
Klaus Wachtler schrieb:> Welche Compileroption hatte der nochmal, um ISO-C99 schreiben zu können?
es ging am C++, gab es da überhaupt ISO-C99 wenn ja ist es aber auch
schon veraltet.
Klaus Wachtler schrieb:> Bei C++ ist er auch nicht viel neuer (C++11)
davon wird aber ein grossteil bereits unterstützt. Ich denke da nehmen
sich MS und GCC nicht viel.
Peter II schrieb:> Klaus Wachtler schrieb:>> Welche Compileroption hatte der nochmal, um ISO-C99 schreiben zu können?>> es ging am C++, gab es da überhaupt ISO-C99 wenn ja ist es aber auch> schon veraltet.
Es handelt sich bei beiden Compilern um kombinierte C- und C++-Compiler.
Was C betrifft: C99 ist mittlerweile quasi veraltet, wird aber vom
Microsoft-Compiler noch immer nicht auch nur ansatzweise unterstützt.
Microsoft setzt weiter auf C90, das es eigentlich seit 12 Jahren nicht
mehr gibt.
> test mit -O3 -march=core2 -mtune=core2:
D. h. mit -mtune wird es noch besser als nur mit -O3 ?
Gilt das auch für -Os ?
Wenn nun -mcpu auf cortex-m3 oder cortex-m4 steht, was würde man dann
für -mtune wählen?
http://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html
vielleicht passend zur Thematik.
ich habe in letzten Tagen auch mit GCC (version 4.5.3)
herumexperementiert.
Interessant sind die Vergleiche zwischen -O1 und -O2 und ohne
Optimierung.
Einmal mit konstantem Parameter, einmal mit einem laufzeitabhängigen
Parameter.
$ cat main.c
1
#include<stdio.h>
2
#include<time.h>
3
4
inty(intx)__attribute__((const));
5
inty(intx){
6
returnx+1;
7
}
8
9
intyy(intx)__attribute__((pure));
10
intyy(intx){
11
returnx+1;
12
}
13
14
intyyy(intx){
15
//printf("%i", time(NULL)); // make sure it has side-effects