Angenommen, wir versuchen, den TSC zur Leistungsüberwachung zu verwenden, und wir wollen verhindern, dass Befehle neu angeordnet werden.Unterschied zwischen Rdtscp, Rdtsc: Speicher und CPU/Rdtsc?
Dies sind unsere Optionen:
1:rdtscp
ist eine Serialisierung Anruf. Es verhindert eine Neuanordnung des Aufrufs von rdtscp.
__asm__ __volatile__("rdtscp; " // serializing read of tsc
"shl $32,%%rdx; " // shift higher 32 bits stored in rdx up
"or %%rdx,%%rax" // and or onto rax
: "=a"(tsc) // output to tsc variable
:
: "%rcx", "%rdx"); // rcx and rdx are clobbered
ist jedoch rdtscp
nur auf neueren CPUs zur Verfügung. In diesem Fall müssen wir rdtsc
verwenden. Aber rdtsc
ist nicht serialisierend, so dass es die CPU nicht daran hindert, sie neu zu ordnen.
So können wir eine dieser beiden Optionen verwenden, um Neuanordnung zu verhindern:
2: Dies ist ein Aufruf an cpuid
und dann rdtsc
. cpuid
ist ein Serialisierungsaufruf.
volatile int dont_remove __attribute__((unused)); // volatile to stop optimizing
unsigned tmp;
__cpuid(0, tmp, tmp, tmp, tmp); // cpuid is a serialising call
dont_remove = tmp; // prevent optimizing out cpuid
__asm__ __volatile__("rdtsc; " // read of tsc
"shl $32,%%rdx; " // shift higher 32 bits stored in rdx up
"or %%rdx,%%rax" // and or onto rax
: "=a"(tsc) // output to tsc
:
: "%rcx", "%rdx"); // rcx and rdx are clobbered
3: Dies ist ein Aufruf an rdtsc
mit memory
in der clobber Liste, die
__asm__ __volatile__("rdtsc; " // read of tsc
"shl $32,%%rdx; " // shift higher 32 bits stored in rdx up
"or %%rdx,%%rax" // and or onto rax
: "=a"(tsc) // output to tsc
:
: "%rcx", "%rdx", "memory"); // rcx and rdx are clobbered
// memory to prevent reordering
Mein Verständnis für die dritte Option ist wie folgt:
die Herstellung Der Aufruf __volatile__
verhindert, dass der Optimierer die ASM entfernt oder sie über irgendwelche Anweisungen bewegt, die die Ergebnisse (oder die Eingaben) der ASM benötigen könnten. Es könnte jedoch immer noch in Bezug auf nicht verwandte Operationen verschoben werden. Also __volatile__
ist nicht genug.
Sagen Sie dem Compilerspeicher wird geplätschert: : "memory")
. Der "memory"
Clobber bedeutet, dass GCC keine Annahmen über Speicherinhalte treffen kann, die über die asm gleich bleiben und daher nicht um sie herum neu angeordnet werden.
So sind meine Fragen:
- 1: Ist mein Verständnis von
__volatile__
und"memory"
richtig? - 2: Machen die zweiten beiden Anrufe dasselbe?
- 3: Die Verwendung von
"memory"
sieht viel einfacher aus als die Verwendung einer anderen Serialisierungsanweisung. Warum sollte jemand die dritte Option über die zweite Option verwenden?
Sie scheinen die Neuordnung der vom Compiler generierten Anweisungen zu verwirren, was Sie vermeiden können, indem Sie 'volatile' und' memory' verwenden und die Anweisungen des Prozessors neu anordnen (aka _out of order execution_), die Sie mit 'vermeiden CPUuid. – hirschhornsalz
@hirschhornsalz aber nicht "Speicher" in der Clobber-Liste verhindern, dass der Prozessor die Anweisungen neu anordnen? Funktioniert "Gedächtnis" nicht wie ein Erinnerungszaun? –
oder vielleicht wird der "Speicher" in der Clobber-Liste nur an gcc ausgegeben, und der resultierende Maschinencode setzt dies dem Prozessor nicht aus? –