Ich habe mir etwas C-Code angesehen
http://www.mcs.anl.gov/~kazutomo/rdtsc.html
Sie verwenden Sachen wie __inline__
, __asm__
usw wie folgt:
Code1:
static __inline__ tick gettick (void) {
unsigned a, d;
__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) );
return (((tick)a) | (((tick)d) << 32));
}
Code2:
volatile int __attribute__((noinline)) foo2 (int a0, int a1) {
__asm__ __volatile__ ("");
}
Ich habe mich gefragt, was Code1 und Code2 tun?
(Anmerkung des Herausgebers: Für diesen speziellen RDTSC-Anwendungsfall werden Intrinsics bevorzugt: How to get the CPU cycle count in x86_64 from C++? Siehe auch https://gcc.gnu.org/wiki/DontUseInlineAsm)
Das __volatile__
Modifikator auf ein __asm__
block zwingt den Optimierer des Compilers, den Code unverändert auszuführen. Ohne sie könnte der Optimierer denken, dass sie entweder direkt entfernt oder aus einer Schleife gehoben und zwischengespeichert werden kann.
Dies ist nützlich für die rdtsc
Anleitung so:
__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) )
Dies erfordert keine Abhängigkeiten, sodass der Compiler davon ausgehen könnte, dass der Wert zwischengespeichert werden kann. Volatile wird verwendet, um es zu zwingen, einen neuen Zeitstempel zu lesen.
Bei alleiniger Verwendung wie folgt:
__asm__ __volatile__ ("")
Es wird nicht wirklich etwas ausgeführt. Sie können dies jedoch erweitern, um eine Speicherbarriere zur Kompilierzeit zu erhalten, die keine Neuordnung von Speicherzugriffsanweisungen zulässt:
__asm__ __volatile__ ("":::"memory")
Das rdtsc
Anweisungen sind ein gutes Beispiel für volatile. rdtsc
wird normalerweise verwendet, wenn Sie die Ausführungszeit einiger Anweisungen messen müssen. Stellen Sie sich einen Code wie diesen vor, wo Sie Zeit haben möchten r1
und r2
Ausführung:
__asm__ ("rdtsc": "=a" (a0), "=d" (d0) )
r1 = x1 + y1;
__asm__ ("rdtsc": "=a" (a1), "=d" (d1) )
r2 = x2 + y2;
__asm__ ("rdtsc": "=a" (a2), "=d" (d2) )
Hier darf der Compiler den Zeitstempel tatsächlich zwischenspeichern, und eine gültige Ausgabe zeigt möglicherweise an, dass jede Zeile genau 0 Takte zur Ausführung benötigt hat. Offensichtlich ist das nicht das, was Sie wollen, also stellen Sie sich vor __volatile__
Caching verhindern:
__asm__ __volatile__("rdtsc": "=a" (a0), "=d" (d0))
r1 = x1 + y1;
__asm__ __volatile__("rdtsc": "=a" (a1), "=d" (d1))
r2 = x2 + y2;
__asm__ __volatile__("rdtsc": "=a" (a2), "=d" (d2))
Jetzt erhalten Sie jedes Mal einen neuen Zeitstempel, aber es besteht immer noch das Problem, dass sowohl der Compiler als auch die CPU alle diese Anweisungen neu anordnen dürfen. Es könnte dazu führen, dass die asm-Blöcke ausgeführt werden, nachdem r1 und r2 bereits berechnet wurden. Um dies zu umgehen, fügen Sie einige Barrieren hinzu, die die Serialisierung erzwingen:
__asm__ __volatile__("mfence;rdtsc": "=a" (a0), "=d" (d0) :: "memory")
r1 = x1 + y1;
__asm__ __volatile__("mfence;rdtsc": "=a" (a1), "=d" (d1) :: "memory")
r2 = x2 + y2;
__asm__ __volatile__("mfence;rdtsc": "=a" (a2), "=d" (d2) :: "memory")
Beachten Sie das mfence
Anweisung hier, die eine CPU-seitige Barriere erzwingt, und der “Speicher”-Spezifizierer im flüchtigen Block, der eine Kompilierzeitbarriere erzwingt. Auf modernen CPUs können Sie ersetzen mfence:rdtsc
mit rdtscp
für etwas Effizienteres.
asm
dient zum Einbinden von nativem Assembly-Code in den C-Quellcode. Z.B
int a = 2;
asm("mov a, 3");
printf("%i", a); // will print 3
Compiler haben verschiedene Varianten davon. __asm__
sollte das auch sein, vielleicht mit Compiler-spezifischen Unterschieden.
volatile
bedeutet, dass die Variable von außen (also nicht vom C-Programm) geändert werden kann. Zum Beispiel beim Programmieren eines Mikrocontrollers, wo die Speicheradresse 0x0000x1234
ist einer gerätespezifischen Schnittstelle zugeordnet (d.h. beim Programmieren für den GameBoy wird auf diese Weise auf Schaltflächen/Bildschirme/usw zugegriffen.)
volatile std::uint8_t* const button1 = 0x00001111;
Dies deaktivierte Compiler-Optimierungen, die darauf angewiesen sind *button1
nicht ändern, es sei denn, sie werden durch den Code geändert.
Es wird auch in der Multithread-Programmierung verwendet (heute nicht mehr benötigt?), wo eine Variable von einem anderen Thread geändert werden kann.
inline
ist ein Hinweis für den Compiler, Aufrufe einer Funktion “inline” zu machen.
inline int f(int a) {
return a + 1
}
int a;
int b = f(a);
Dies sollte nicht in einen Funktionsaufruf kompiliert werden f
aber hinein int b = a + 1
. Als ob f
wo ein Makro. Compiler führen diese Optimierung meist automatisch je nach Funktionsnutzung/Inhalt durch. __inline__
in diesem Beispiel könnte eine spezifischere Bedeutung haben.
Ähnlich __attribute__((noinline))
(GCC-spezifische Syntax) verhindert, dass eine Funktion eingebettet wird.
Das __asm__
Das Attribut gibt den Namen an, der im Assembler-Code für die Funktion oder Variable verwendet werden soll.
Das __volatile__
Qualifier, der im Allgemeinen im Real-Time-Computing eingebetteter Systeme verwendet wird, adressiert ein Problem mit Compiler-Tests der status register
für die ERROR
oder READY
Bit verursacht Probleme während der Optimierung. __volatile__
wurde eingeführt, um dem Compiler mitzuteilen, dass das Objekt schnellen Änderungen unterliegt, und um zu erzwingen, dass jede Referenz des Objekts eine echte Referenz ist.
gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html (ansonsten ignorieren
__
überall, überallhin, allerorts,__inline__
ist einfach schlichtinline
.– Marc Glisse
20. Oktober 2014 um 11:44 Uhr