Что случилось с поведением rdtscp наполовину?

В течение многих лет процессоры 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 бита счетчика.


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