В течение многих лет процессоры 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 бита счетчика.
lfence
поведения сериализации инструкций иlfence; rdtsc
так широко, когда RDTSCP был разработан, как сейчас (после Spectre)? IDK, если Intel позаботилась о переносимой (для AMD) гарантии, чтоrdtscp
может использоваться в конце временных областей без включения жестких инструкций сериализации, таких какcpuid
, после того, как AMD внедрила это. Это кажется маловероятным, но, возможно, целью было убедиться, что люди смогут избежатьcpuid;rdtsc
с процессорами Intel. (cpuid;rdtsc
в верхней части синхронизированной области подходит, потому чтоcpuid
находится за пределами временной области.) - person Peter Cordes   schedule 04.09.2018lfence;rdtsc;lfence
обычно является хорошей вещью в верхней части временной области, поэтому он производит выборку времени перед запуском временной области. - person Peter Cordes   schedule 04.09.2018rdtscp
можно использовать, чтобы определить, перекрываются ли две критические секции или части некоторых транзакций, и что-то предпринять, если это произошло. Я считаю, что для этой цели требуется возможность сериализации предыдущих загрузок и определения основных миграций. Сериализация последующих загрузок не требуется. См. this и this. Кроме того, это также может быть полезно для устойчивости к сбоям. Но я мало что знаю об этой области, поэтому не хочу писать ответ. - person Hadi Brais   schedule 04.09.2018rdtscp
в современных реализациях фактически не имеет поведения половинного забора, а скорее полного забора, такого какlfence
: инструкции для любого размера вызоваrdtscp
, похоже, не могут перекрываться. - person BeeOnRope   schedule 24.06.2019