Предположим, мы пытаемся использовать tsc для мониторинга производительности, и мы хотим предотвратить переупорядочение команд.
Это наши варианты:
1: rdtscp
- это сериализующий вызов. Это предотвращает переупорядочивание по вызову 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
Однако rdtscp
доступен только для новых процессоров. Поэтому в этом случае мы должны использовать rdtsc
. Но rdtsc
не сериализуется, поэтому его использование не будет препятствовать переупорядочиванию ЦП.
Таким образом, мы можем использовать любой из этих двух параметров, чтобы предотвратить переупорядочение:
2: Это вызов cpuid
, а затем rdtsc
. cpuid
- сериализующий вызов.
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: Это вызов rdtsc
с memory
в списке clobber, который предотвращает переупорядочивание
__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
Мое понимание для третьего варианта выглядит следующим образом:
Выполнение вызова __volatile__
не позволяет оптимизатору удалять asm или перемещать его по любым инструкциям, которые могут потребовать результаты (или изменить входы) asm. Однако он все равно может переместить его в отношении несвязанных операций. Так что __volatile__
недостаточно.
Скажите, что память компилятора сбита: : "memory")
. Clobber "memory"
означает, что GCC не может делать какие-либо предположения о том, что содержимое памяти остается неизменным в asm и, следовательно, не будет изменять порядок вокруг него.
Итак, мои вопросы:
- 1: Правильно ли мое понимание
__volatile__
и"memory"
? - 2: выполняют ли два вторых вызова одно и то же?
- 3: Использование
"memory"
выглядит намного проще, чем использование другой инструкции сериализации. Зачем кому-то использовать третий вариант над вторым вариантом?