Ответ 1
Модификатор __volatile__
в блоке __asm__
заставляет оптимизатор компилятора выполнять код как есть. Без него оптимизатор может подумать, что он может быть удален или удален из цикла и кэширован.
Это полезно для команды rdtsc
, например:
__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) )
Это не требует зависимостей, поэтому компилятор может предположить, что значение может быть кэшировано. Volatile используется, чтобы заставить его читать новую метку времени.
При использовании в одиночку, например:
__asm__ __volatile__ ("")
На самом деле ничего не исполнится. Однако вы можете расширить это, чтобы получить барьер памяти с компиляцией, который не позволит переупорядочить инструкции доступа к памяти:
__asm__ __volatile__ ("":::"memory")
Инструкция rdtsc
является хорошим примером нестабильности. rdtsc
обычно используется, когда вам нужно время, какое количество команд требуется выполнить. Представьте себе такой код, где вы хотите выполнить время r1
и r2
:
__asm__ ("rdtsc": "=a" (a0), "=d" (d0) )
r1 = x1 + y1;
__asm__ ("rdtsc": "=a" (a1), "=d" (d1) )
r2 = x2 + y2;
__asm__ ("rdtsc": "=a" (a2), "=d" (d2) )
Здесь компилятору разрешено кэшировать временную метку, а допустимый вывод может показать, что каждая строка занимает ровно 0 тактов. Очевидно, это не то, что вы хотите, поэтому вы вводите __volatile__
для предотвращения кеширования:
__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))
Теперь вы будете получать новую временную метку каждый раз, но у нее все еще есть проблема, что и компилятор, и процессор могут изменить порядок всех этих операторов. Это может привести к выполнению блоков asm после того, как r1 и r2 уже были рассчитаны. Чтобы обойти это, вы должны добавить некоторые барьеры, которые вызывают сериализацию:
__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")
Обратите внимание на инструкцию mfence
здесь, которая обеспечивает барьер на стороне процессора и спецификатор "памяти" в энергозависимом блоке, который обеспечивает защиту от компиляции. На современных процессорах вы можете заменить mfence:rdtsc
на rdtscp
для чего-то более эффективного.