Forum: PC-Programmierung Performance des GCC


von Bernd (Gast)


Lesenswert?

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
1
for (j = 0; j < 10000; j++) {
2
  for (i = 0; i < 100000; i++) {
3
        C[i] = A[i] + B[i];
4
    }
5
}

ASM
1
xor     edx, edx
2
loc_401317:
3
xor     ebx, ebx
4
lea     esi, [esi+0]
5
loc_401320:
6
mov     eax, [ebp+ebx*4+var_A]
7
mov     ecx, [ebp+ebx*4+var_B]
8
add     eax, ecx
9
mov     [ebp+ebx*4+var_C], eax
10
inc     ebx
11
cmp     ebx, 1869Fh
12
jle     short loc_401320
13
inc     edx
14
cmp     edx, 270Fh
15
jle     short loc_401317

von Bartli (Gast)


Lesenswert?

> x86 mingw 3.4.5

Na da hast du ja einen ganz aktuellen gcc...

von Bernd (Gast)


Lesenswert?

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?

von Roland H. (batchman)


Lesenswert?

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.

von Bernd (Gast)


Lesenswert?

Ok, hab mir mal einen neuen GCC installiert, Version 4.6.1 ebenfalls mit 
-O3 und -ansi übersetzt.

C-Programm:
1
int main ()
2
{
3
  volatile int A[100000];
4
  volatile int B[100000];
5
  volatile int C[100000];
6
  int i, 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
  return 0;
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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Bernd (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Bernd (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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...

von Bernd (Gast)


Lesenswert?

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.

von Sven P. (Gast)


Lesenswert?

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.

von Uwe (Gast)


Lesenswert?

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 ?

von Dussel (Gast)


Lesenswert?

>Es gibt zehnzeilige C++-Quelltexte, die mehrere Stunden kompilieren
Standard C++? Hast du da ein Beispiel? Würde mich mal interessieren.

von Peter II (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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 :-)

von Sven P. (Gast)


Lesenswert?

1
struct a {
2
  typedef int foo;
3
};
4
5
struct a1: a{
6
};
7
8
struct a2: a{
9
};
10
11
#define X(p,q)        \
12
  struct q##1: p##1, p##2 {  \
13
  };        \
14
  struct q##2: p##1, p##2 {  \
15
  };
16
17
X(a,b) X(b,c) X(c,d) X(d,e) X(e,f) X(f,g) X(g,h) X(h,i) X(i,j) X(j,k) X(k,l) X(l,m) X(m,n)
18
19
n1::foo main(){
20
}

Siehe Fefe sein Blog (http://www.fefe.de/c++/)

von Dussel (Gast)


Lesenswert?

Tatsächlich. "Virtual memory exhaustet. Cannot allocate memory"
Ist ja interessant. Danke

von Peter II (Gast)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

Welche Compileroption hatte der nochmal, um ISO-C99 schreiben zu können?

von Peter II (Gast)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

Bei C++ ist er auch nicht viel neuer (C++11).

von Peter II (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Roland H. (batchman)


Lesenswert?

> 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

von Daniel -. (root)


Lesenswert?

Rolf Magnus schrieb:
> Hmm, also mein gcc 4.5.2 macht da folgendes draus:
> (AT&T-Syntax, da Linux):
1
objdump.exe -d main.o -j .text -M intel

mittlerweile habe ich mich an AT&T Syntax gewöhnt^^

von Daniel -. (root)


Lesenswert?

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
int y(int x)__attribute__((const));
5
int y(int x) {
6
        return x+1;
7
}
8
9
int yy(int x)__attribute__((pure));
10
int yy(int x) {
11
        return x+1;
12
}
13
14
int yyy(int x) {
15
        //printf("%i", time(NULL));   // make sure it has side-effects
16
        return x+1;
17
}
18
19
int main() {
20
        int x1,x2,x3,x4,x5,x6;
21
        int x;
22
        scanf("%i", &x);
23
        x1=y(x);
24
        x2=y(x);
25
        x3=yy(x);
26
        x4=yy(x);
27
        x5=yyy(x);
28
        x6=yyy(x);
29
        // x1=y(1);
30
        // x2=y(1);
31
        // x3=yy(1);
32
        // x4=yy(1);
33
        // x5=yyy(1);
34
        // x6=yyy(1);
35
        printf("%i-%i\n", x1,x2);
36
        printf("%i-%i\n", x3,x4);
37
        printf("%i-%i\n", x5,x6);
38
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.