Предположим, мы пытаемся использовать 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"выглядит намного проще, чем использование другой инструкции сериализации. Зачем кому-то использовать третий вариант над вторым вариантом?