Schnelligkeit von C-Code
-
SeppJ schrieb:
Aber dann ist es doch keine Registervariable mehr, wenn sie im Hauptspeicher lebt und bei Bedarf in Register geladen wird! Es ist eine ganz normale Variable, für die man ein bevorzugtes Register angegeben hat.
Bei modulübergreifenden Programme - ja.
Aber bei Nutzung innerhalb eines Moduls - insbesondere innerhalb des Moduls, welchesmain
beinhaltet - kann der Compiler Optimierungen durchführen, um r12-r15 nicht zwischenspeichern zu müssen. Wie bei statischen globalen Variablen, die nach Außen hin nicht sichtbar sind - wenn manfunc
inmain.c
lässt, kann man sehen, dassfunc
die Variablen lieber auf dem Stack lässt, als r12-r15 zwischenzuspeichern.Und ich habe mir nochmal die Mühe gemacht, mit LTO zu kompilieren - und damit lässt
func
dieses Register auch in Ruhe.
-
dachschaden schrieb:
Und ich habe mir nochmal die Mühe gemacht, mit LTO zu kompilieren - und damit lässt
func
dieses Register auch in Ruhe.Schick.
Jetzt müssen wir nur noch messen, wie hoch der Performanceeinbruch ist, wenn man den Compiler auf diese Weise zwingt, permanent ein Register weniger für alles zur Verfügung zu haben
-
dachschaden schrieb:
Wie sinnvoll es ist, eine einzige Variable für jeden Thread in einem Register zu halten, lassen wir mal dahingestellt.
Ich habe nicht vor, den Sinn und Zweck, oder Performancegewinne oder -Verluste nachzuweisen.
Mir ging es nur darum, aufzuzeigen, dass der GCC sowas kann.Und ich würde mir verdammt gut überlegen, ein Register auf diese Art und Weise zu belegen. Normalerweise weiß der Compiler nämlich, was er tut.
-
@Sepp, @Hansklaus
Könnt ihr beide bitte ordentlich auf meine noch offene Frage antworten - erst kommt HansKlaus mit dieser x mal schneller mit Watcom und inline-Assembler Sache, gibt mehr Details und beim Nachstellen zeigt sich ein völlig anderes Bild - leicht unbefriedigend wenn dann gar kein Feedback mehr kommt
Sie haben halt einmal von Hand geinlined und beim anderen Mal eine für den Compiler opaque Funktion aufgerufen. Sehr überraschendes Ergebnis
ich glaube du hast mein Ergebnis falsch interpretiert:
wenn man HansKlaus Beispiel mit Open Watcom+Optimierung kompiliert ist die inline-Asm-Version langsamer und die C-Version ist kürzer/schneller (inlined + weniger Opcodes)
Es ist ein wenig überraschend das HansKlaus Aussage nach meinen Tests einfach nicht stimmt
@Hansklaus
ich würde gerne Wissen was damals (möglicherweise) schief gelaufen ist (z.B. Debug-Build gebenchmarkt?) oder ob der Open Watcom viel älter/schlechter war (obwohl an der Optimierung seit Veröffentlichung kaum gearbeitet wurde)
Ich stolpere häufiger über solche Aussagen bei Kollegen/Azubis und gehen denen dann gerne vollständig auf den Grund um Mythenbildung im Keim zu ersticken oder um wirklich erklären zu können warum es schneller istWarum er Unterschied zu deiner Aussage?
-
die programme wurden über com-schnittstelle auf eine von der hochschule selbst gebaute kiste mit einem 386ex embedded übertragen und dort dann ausgeführt.
das signal wurde gewissermaßen direkt von der cpu abgenommen und mit einem oszilloskop ausgewertet.optimierungsflags wurden keine verwendet und der compiler war evtl. schon etwas älter - eben der, der sich da im labor auf dem rechner befand.
aber wenn ich mir die assemblercodes so ansehe, ist der inline-assembler nur deshalb langsamer, weil der programmierer offenbar der meinung war, dass das register mit der portadresse jedes mal neu beschrieben werden muss.
edit: habe es geschafft, mir den openwatcom compiler raufzuziehen und die programme alle noch einmal durch den compiler zu schieben.
original programmcodes:
#include <conio.h> //Aufgabe21.c #define EVER ;; #define LPT 0x338 int main() { for(EVER) { outp(LPT,0x01); outp(LPT,0x00); } }
#include <conio.h> //Aufgabe22.c #define EVER ;; #define LPT 0x338 int main() { for(EVER) { _asm { mov al,0x01 mov dx,LPT out dx,al mov al,0x00 mov dx,LPT out dx,al } } }
D:\Studium\MCT\Labor\Übung 1>wcl -q -ot aufgabe21
Aufgabe21.c(13): Warning! W138: No newline at end of fileD:\Studium\MCT\Labor\Übung 1>wcl -q -ot aufgabe22
D:\Studium\MCT\Labor\Übung 1>wdis aufgabe21
Module: D:\Studium\MCT\Labor\▄bung 1\Aufgabe21.c
GROUP: 'DGROUP' CONST,CONST2,_DATASegment: TEXT PARA USE16 0000001A bytes
0000 main:
0000 B8 04 00 mov ax,0x0004
0003 E8 00 00 call STK
0006 52 push dx
0007 L$1:
0007 BA 01 00 mov dx,0x0001
000A B8 38 03 mov ax,0x0338
000D E8 00 00 call outp
0010 31 D2 xor dx,dx
0012 B8 38 03 mov ax,0x0338
0015 E8 00 00 call outp
0018 EB ED jmp L$1Routine Size: 26 bytes, Routine Base: _TEXT + 0000
No disassembly errors
Segment: CONST WORD USE16 00000000 bytes
Segment: CONST2 WORD USE16 00000000 bytes
Segment: _DATA WORD USE16 00000000 bytes
D:\Studium\MCT\Labor\Übung 1>wdis aufgabe22
Module: D:\Studium\MCT\Labor\▄bung 1\Aufgabe22.c
GROUP: 'DGROUP' CONST,CONST2,_DATASegment: TEXT PARA USE16 00000019 bytes
0000 main:
0000 B8 0C 00 mov ax,0x000c
0003 E8 00 00 call __STK
0006 53 push bx
0007 51 push cx
0008 52 push dx
0009 56 push si
000A 57 push di
000B L$1:
000B B0 01 mov al,0x01
000D BA 38 03 mov dx,0x0338
0010 EE out dx,al
0011 B0 00 mov al,0x00
0013 BA 38 03 mov dx,0x0338
0016 EE out dx,al
0017 EB F2 jmp L$1Routine Size: 25 bytes, Routine Base: _TEXT + 0000
No disassembly errors
Segment: CONST WORD USE16 00000000 bytes
Segment: CONST2 WORD USE16 00000000 bytes
Segment: _DATA WORD USE16 00000000 bytes
D:\Studium\MCT\Labor\Übung 1>
gut das unterprogramm heißt nicht chk sondern outp, aber wenn der assemblercode nicht so schlecht wäre, wäre das programm mit dem inline-assembler noch schneller und kürzer
-
gut das unterprogramm heißt nicht chk sondern outp, aber wenn der assemblercode nicht so schlecht wäre, wäre das programm mit dem inline-assembler noch schneller und kürzer
wenn unter realistischen Bedingungen verglichen würde ich dir recht geben - aber das habt ihr nicht und ich verstehe nicht was diese Aufgabe aufzeigen sollte
nur als Info weil du scheinbar keinerlei Problem mit dem Testszenario und den Erkenntnissen daraus zu haben scheinst
Es gibt um mal mit VStudio Begriffen um sich zu werfen den sog. Debug- und den Release-Mode (oder z.B. sowas wie -O0 und -O2 bei gcc und Konsorten)
Debug-Mode bedeutet das der Kompiler angewiesen wird möglich wenig Optimierungen durchzuführen damit der Debugger in Normalfall alle Symbole die im Quelltext vorkommen auch finden und zuordnen kann - als Nebeneffekt wird der Code dadurch auch schneller kompiliert ist aber sehr sehr langsam, der generierte Code sieht fast so aus als hätte man jede einzelne Zeile für sich in Assembler übersetzt - sehr stupider langer Code
Release-Mode bedeutet das der Kompiler angewiesen wird möglichst schnellen Code zu erzeugen d.h. inlining von kurzen Funktionen, Konstantfolding und,und,und...(die Liste der Optimerungen ist lang und wird ständig länger) - Nachteil sind das der Kompiliervorgang um ein vielfaches länger dauern kann und das Kompilat ist schwerer zu debuggen weil einfach viele Symbole durch die Optimierung wegoptimiert(z.B. mit anderen vereint) werden und der Debugger dann "kann nicht aufgelöst werde..." Anzeigt - dafür ist der Code aber sehr sehr schnell - die Geschwindigkeitssteigerung gegenüber dem Debug-Mode sind normalerweise sehr drastisch
Es gibt viele Anfänger (hab aber auch schon gestandene Entwickler erlebt) die das nicht wissen und denke der Kompiler wird wohl immer das beste Ergebnis erzeugen - was bei keinem C/C++ Kompiler so zutrifft
-
2. Teil - zu früh auf Absenden geklickt
ihr habt also den Kompiler gezwungen (aus Geschwindigkeitssicht) möglichst schlechten Code zu erzeugen (weil keine Optmierung gefordert wrude) und habt das dann mit einem handwerklich gutem inline-Assembler-Coder verglichen - das alter des Watcom Kompiler ist dafür unrelevant weil die Nicht-Optimierung sowieso immer maximal einfachen/langsamen Code erzeugt damit der Debugger besser arbeiten kann
Was also sollte dieser Test zeigen/beweisen?
Jeder kann bei einem Wettrennen gewinnen wenn er dem anderen vorher erstmal die Beine bricht
und schön das ihr es am andere Ende mit einem Ozi gemessen habt - ändert nur leider nichts daran das ihr etwas optimiert habt was der Kompiler (richtig verwendet) besser gemacht hätte
Oft wird einfach vergessen die Optimierung zu aktivieren (was absoluter Standard in der Branche ist) oder der Benchmark-Code macht nicht das was man denkt
-
naja die aufgabe sollte wohl zeigen, dass inline-assembler schneller ist bzw. sein kann, weil sich dieser im gegensatz zu hochsprachen unmittelbar in opcodes umwandeln lässt.
es gab auch noch eine weitere aufgabe, bei der wir das ganze mit hilfe eines ominösen treibers, der sich aber genauso verhält wie in und out, auf einem windows-pc durchführen und dann erklären sollten, wieso das signal in bezug auf die viel höhere taktfrequenz der cpu so unglaublich langsam ist.
die aufgabe sollte uns dann zeigen, dass in und out heutzutage ziemlich selten zu anwendung kommen, weil das betriebssystem alles sperrt und außerdem die hardware gar nicht mehr direkt an die cpu angeschlossen ist.jedenfalls hast du beim openwatcom keinen debugmodus bzw. du musst da explizit angeben, dass du debuginformationen im programm haben willst, ansonsten ist das immer "release".
-
HansKlaus schrieb:
die aufgabe sollte uns dann zeigen, dass in und out heutzutage ziemlich selten zu anwendung kommen, weil das betriebssystem alles sperrt und außerdem die hardware gar nicht mehr direkt an die cpu angeschlossen ist.
Was ist Real Mode?
HansKlaus schrieb:
jedenfalls hast du beim openwatcom keinen debugmodus bzw. du musst da explizit angeben, dass du debuginformationen im programm haben willst, ansonsten ist das immer "release".
Das gleiche beim GCC, und dennoch musst du da auch die Optimierungsstufe mit
-O<X>
angeben. Ohne Optimierungsstufe wird auch fast C-2-Assembler kompiliert, erst mit Optimizern geht der Compiler den Code mal mit Verstand durch.EDIT: Debugging-Informationen bekommst du mit
-g -ggdb3
, für's Profiling gibt's noch-pg
.
-
da ich das andere eben nicht gesehen habe: ich habe doch den optimierten code (-ot = optimize for time) hochgeladen und da ist doch eindeutig zu sehen, dass da ständig irgendwelche unterprogramme aufgerufen werden.
-
HansKlaus schrieb:
da ist doch eindeutig zu sehen, dass da ständig irgendwelche unterprogramme aufgerufen werden.
Du meinst, das hier?
L$1: mov al,0x01 mov dx,0x0338 out dx,al mov al,0x00 mov dx,0x0338 out dx,al jmp L$1
Ich sehe vier
mov
s, zweiout
s, und einenjmp
. Unterprogramme (/Funktionsaufrufe) sehe ich da nicht.EDIT: Nein, ich bin blöd, du hast beide mit
-ot
kompilert. Aber schauen wir uns mal den Code dennoch an:L$1: mov dx,0x0001 mov ax,0x0338 call outp_ xor dx,dx mov ax,0x0338 call outp_ jmp L$1
Die einzigen Unterschied sind, dass statt
out
als Instruktionoutp_
als Funktion aufgerufen wird, und dass statt einmov
einxor
generiert wird. Die Frage ist, warum der Compiler denn sowas tut - oder anders, wennoutp_
langsamer ist als die Instruktion, warum er dann nichtout
direkt verwendet.
-
naja die aufgabe sollte wohl zeigen, dass inline-assembler schneller ist bzw. sein kann, weil sich dieser im gegensatz zu hochsprachen unmittelbar in opcodes umwandeln lässt.
Das zeigt euer Beispiel aber eben nicht - nur das ein falsch konfigurierter Kompiler schlechten Code erzeugt - mehr nicht
jedenfalls hast du beim openwatcom keinen debugmodus bzw. du musst da explizit angeben, dass du debuginformationen im programm haben willst, ansonsten ist das immer "release".
Codeoptmierung und Mit-Debug-Information sind unterschiedliche Dinge - wenn du beim Watcom keine Optimierangabe machst optimiert er nichts - so wie jeder andere (ältere oder neuere) C/C++ Kompiler den ich kenne auch - und du kannst immer zusätzlich Debug-Information anfordern (unabhängig von der Optimierung)
Kompilier doch einfach mal mit "Standard" Optimiersettings:
z.B. wcl -q -bt=DOS /oneatx /oh /ei /zp8 /6 /fp6 d:\temp\outp_test.c
findest du dann deine calls noch?
und dein Inline-Assembler schlägt z.B. auch den neuesten GCC 6.2 (vom 22.8.2016), VS2015 oder Clang 3.8 - wenn du ihn ohne Optimierung aufrufst
#include <stdint.h> #define PORT 123 static inline void outp(uint16_t port, uint8_t val) { asm ( "outb %0, %1" : : "a"(val), "Nd"(port) ); } int main() { //Hier noch bisschen Initialisierung for(;;) { outp(PORT,0x01); outp(PORT,0x00); } return 0; }
Ohne Optimierung
gcc -O0 (oder keine Angabe) - sieht (ungefähr - bis auf 32bit) so aus wie dein Ergebnis mit den 2 Callsoutp(unsigned short, unsigned char): push rbp mov rbp, rsp mov edx, edi mov eax, esi mov WORD PTR [rbp-4], dx mov BYTE PTR [rbp-8], al movzx eax, BYTE PTR [rbp-8] movzx edx, WORD PTR [rbp-4] outb al, dx nop pop rbp ret main: push rbp mov rbp, rsp .L3: mov esi, 1 mov edi, 123 call outp(unsigned short, unsigned char) mov esi, 0 mov edi, 123 call outp(unsigned short, unsigned char) jmp .L3
mit Standard-Optimierung
gcc -02 -> sieht so aus wie das Watcom Ergebnis mit den Optimierflags kompiliertmain: mov ecx, 1 xor edx, edx .L2: mov eax, ecx outb al, 123 mov eax, edx outb al, 123 jmp .L2
-
dachschaden schrieb:
Was ist Real Mode?
ein zustand in dem man vollen zugriff auf die hardware hat und sehr wenig speicher ansprechen kann. 64kiB oder 1MiB, weiß ich jetzt grad nicht.
wird vom betriebssystem soweit ich weiß komplett gesperrt.Das gleiche beim GCC, und dennoch musst du da auch die Optimierungsstufe mit
-O<X>
angeben. Ohne Optimierungsstufe wird auch fast C-2-Assembler kompiliert, erst mit Optimizern geht der Compiler den Code mal mit Verstand durch.hmmm also wir sollen später auch noch lernen, wie man so etwas von hand optimiert bzw. wie man ressourcenschonend programmiert. der compiler hat sich das ja auch nicht ausgedacht, sondern irgendjemand hat das so programmiert, dass diese ptimierungen durchgeführt werden.
wie dem auch sei: wenn ich daran denke, compiliere ich es gerne noch einmal mit optimierung und messe die frequenzen erneut. ich bezweifle aber, dass diese kleinen kniffe, die der compiler da anwendet bei einer cpu mit 30MHz so stark ins gewicht fallen, dass aus 120kHz ausgangssignal plötzlich 300kHz oder noch mehr werden.
-
dachschaden schrieb:
Die einzigen Unterschied sind, dass statt
out
als Instruktionoutp_
als Funktion aufgerufen wird, und dass statt einmov
einxor
generiert wird. Die Frage ist, warum der Compiler denn sowas tut - oder anders, wennoutp_
langsamer ist als die Instruktion, warum er dann nichtout
direkt verwendet.xor ist eben schneller als mov und out_ wird zumindest laut aussage des laboringenieurs deshalb aufgerufen, weil der compiler eben nicht weiß, was er genau machen soll, während ich ihm bei inline-assembler gewissermaßen erkläre, dass ich so schlau bin und er bitte den assembler-code direkt übernehmen soll - was er dann ja auch so macht.
-
HansKlaus schrieb:
ein zustand in dem man vollen zugriff auf die hardware hat und sehr wenig speicher ansprechen kann. 64kiB oder 1MiB, weiß ich jetzt grad nicht.
Ein Mebibyte - 16 64-KiB-Segmente. Und das ist der Adressraum, nicht der insgesamt ansprechbare Arbeitsspeicher (der ist 10 64-KiB-Segmente, 640 KiB.)
Jeder Kernel auf einem x86/x64-System bootet im Real Mode. Die Hardware MUSS an die CPU angeschlossen sein. Nur, weil das Betriebssystem den Zugriff auf die Hardware für Userspace-Programme sperrt, heißt das nicht, dass der Zugriff nicht mehr möglich ist.
Auch moderne x64-Prozessoren haben auch noch das A20-Gate. Es gibt eine Meeeenge Abwärtskompatibilität in Prozessoren, auch die Taktfrequenzen, in denen kommuniziert wird.
EDIT:
HansKlaus schrieb:
xor ist eben schneller als mov
Ein
xor
spart Platz, ist aber nicht unbedingt schneller. Das hängt davon ab, wie die CPU funktioniert. Die kann bei der Dekodierung von Instuktionen, die eine fixe Länge haben, schneller sein. Deswegen macht man auch die Unterscheidung zwischen "Optimierung nach Größe" und "Optimierung nach Geschwindigkeit".
-
Gast3 schrieb:
Kompilier doch einfach mal mit "Standard" Optimiersettings:
z.B. wcl -q -bt=DOS /oneatx /oh /ei /zp8 /6 /fp6 d:\temp\outp_test.c
das betriebssystem ist aber nicht dos und die cpu ist auch kein pentium pro.
-
ein zustand in dem man vollen zugriff auf die hardware hat und sehr wenig speicher ansprechen kann. 64kiB oder 1MiB, weiß ich jetzt grad nicht.
wird vom betriebssystem soweit ich weiß komplett gesperrt.dachschaden weiss garantiert zu 100% was Real Mode bedeutet
mir auch zu 100% klar was Real Mode bedeutet - und ich habe diesen auch mit dem Watcom schon vor 24 Jahren programmiert, und mit Turbo/Borland C, Microsoft C, Paradigm C - weit vor der 32 Bit Zeit - jetzt eben nur noch ein paar (Sicherheits)Ringe drüber - und mit anderen Kompilern und Betriebssystemen
-
das betriebssystem ist aber nicht dos und die cpu ist auch kein pentium pro.
das ist völlig irelevant - out und mov haben sich seit dem 8088 nicht geändert - wie du auch am 32/64bit Opcodes des GCCs sehen kannst
aber stell doch einfach die richtigen Parameter ein und sieh das es das bessere Ergebnis null verändert
-
und ob DOS oder Windows ist für die Code-Generierung auch völlig irelevant - nur das Exe-Image sieht leicht anders aus - was nicht bedeutet das du es ausführen kannst - aber der generierte Code ist mehr oder minder der selbst - auf jeden Fall aus Geschwindigkeitssicht
ausserdem müsste es DOS sein - oder wie nutzt du denn sonst out ohne Ring 0 Execptions zu bekommen?
-
386ex embedded übertragen...
Achso - also eben kein DOS sonder was eigenes was eben im Real-Mode betrieben wird
also wäre die CPU schon mal ein 386ex - was ist dann dein Target-OS - oder erzeugt ihr nur obj und kombiniert die irgendwie fuer das embedded 386ex OS?Ist ja nicht so das ich mich damit nicht Auskennen würde