В течение многих лет процессоры x86 поддерживали инструкцию rdtsc
, которая считывает «счетчик меток времени» текущего процессора. Точное определение этого счетчика со временем менялось, но на последних процессорах это счетчик, который увеличивается с фиксированной частотой относительно времени настенных часов, поэтому он очень полезен как строительный блок для быстрых, точных часов или измерения времени. занято небольшими сегментами кода.
Один важный факт в инструкции rdtsc
не упорядочен каким-либо особым образом с окружающим кодом. Как и большинство инструкций, он может быть свободно переупорядочен относительно других инструкций, с которыми он не находится в зависимости. На самом деле это «нормально», и для большинства инструкций это просто невидимый способ ускорить ЦП (это просто длинный способ сказать внеочередное выполнение ).
Для rdtsc
это важно, потому что это означает, что вы можете не синхронизировать код, который вы ожидаете. Например, с учетом следующей последовательности 1 :
rdtsc
mov ecx, eax
mov rdi, [rdi]
mov rdi, [rdi]
rdtsc
Вы можете ожидать, что rdtsc
измерит задержку двух нагрузок загрузки указателя mov rdi, [rdi]
. На практике, однако, даже если обе эти загрузки требуют времени просмотра (100 секунд циклов, если они отсутствуют в кэше), вы получите довольно небольшое чтение для пары rdtsc
. Проблема в том, что второй rdtsc
не ждет окончания загрузки, он просто выполняется не по порядку, поэтому вы не синхронизируете интервал, который вы считаете нужным. Возможно, обе инструкции rdtsc
фактически даже выполняются до начала первой загрузки, в зависимости от того, как rdi
было вычислено в коде до этого примера.
Пока это звучит скорее как ответ на вопрос, который никто не задавал, чем на реальный вопрос, но я получаю.
У вас есть два основных варианта использования для rdtsc
:
- Как быстрая временная метка, в которой вы обычно не заботитесь о том, как именно он переупорядочивается с окружающим кодом, поскольку у вас, вероятно, нет понятия уровня инструкции, где должна быть взята временная метка.
В качестве точного механизма синхронизации, например, в микротесте. В этом случае вы обычно защищаете свой rdtsc
от повторного заказа с помощью инструкции lfence
. В приведенном выше примере вы можете сделать что-то вроде:
lfence
rdtsc
lfence
mov ecx, eax
...
lfence
rdtsc
Чтобы гарантировать, что синхронизированные инструкции (...
) не выходят за пределы временной области, а также чтобы гарантировать, что инструкции изнутри временной области не входят (вероятно, это меньше проблем, но они могут конкурировать за ресурсы с кодом, который вы хотите измерить).
Спустя годы Intel посмотрела на нас, бедных программистов, свысока и предложила новую инструкцию: rdtscp
. Как и rdtsc
, он возвращает показание счетчика меток времени, и этот парень делает что-то большее: он читает значение MSR, специфичное для ядра, атомарно с чтением метки времени. В большинстве операционных систем это содержит значение идентификатора ядра. Я думаю, что идея заключается в том, что это значение можно использовать для правильной настройки возвращаемого значения в реальном времени на процессорах, которые могут иметь разные смещения TSC на ядро.
Отлично.
Другая вещь, введенная rdtscp
, была полуфехтование с точки зрения выполнения вне порядка:
Из руководства :
Инструкция RDTSCP не является командой сериализации, но она делает
дождаться выполнения всех предыдущих инструкций и всех предыдущих
грузы видны глобально.1 Но это не ждет предыдущих магазинов
быть глобально видимым, и могут начаться последующие инструкции
выполнение до выполнения операции чтения.
Так что это все равно что поставить lfence
перед rdtscp
, но не после. В чем смысл этого полузащитного поведения? Если вам нужна общая временная метка, и вы не заботитесь о порядке следования инструкций, вам нужно безусловное поведение. Если вы хотите использовать это для синхронизации коротких фрагментов кода, то поведение половинного ограждения полезно только для второго (окончательного) чтения, но не для начального чтения, так как ограничитель находится на «неправильной» стороне (на практике вы хотите ограждения с обеих сторон, но, вероятно, самое важное - иметь их изнутри).
Какой цели служит такое фехтование?
1 В этом случае я игнорирую старшие 32 бита счетчика.