Получение циклов процессора с помощью RDTSC-почему значение RDTSC всегда увеличивается?


Я хочу получить циклы процессора в определенной точке. Я использую эту функцию в этой точке:

static __inline__ unsigned long long rdtsc(void)
{
    unsigned long long int x;
    __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
    return x;
}

Проблема в том, что он всегда возвращаетвозрастающее число (в каждом запуске). Это как если бы оно относилось к абсолютному времени.

Я неправильно использую функции?

3 14

3 ответа:

Пока поток остается на том же ядре процессора, инструкция RDTSC будет продолжать возвращать все большее число, пока не обернется. Для процессора 2 ГГц это происходит через 292 года, поэтому это не является реальной проблемой. Вы, вероятно, не увидите, как это произойдет. Если вы рассчитываете прожить так долго, убедитесь, что ваш компьютер перезагружается, скажем, каждые 50 лет.

Проблема с RDTSC заключается в том, что у вас нет гарантии, что он запускается в один и тот же момент времени на всех ядрах старого многоядерного процессора и нет гарантируйте, что он запускается в один и тот же момент времени на всех процессорах на старой плате с несколькими процессорами.
Современные системы обычно не имеют таких проблем, но эту проблему можно обойти и на старых системах, установив сходство потока, чтобы он работал только на одном процессоре. Это не очень хорошо для производительности приложения, поэтому обычно не следует этого делать, но для измерения тиков это просто прекрасно.

(другая "проблема" заключается в том, что многие люди используют RDTSC для измерения времени, которое является не то, что он делает, но вы написали, что вам нужны циклы процессора, так что это нормально. Если вы действительно используете RDTSC для измерения времени, у вас могут быть сюрпризы, когда экономия энергии или гиперболизация или что-то еще из множества методов изменения частоты называют ударами. Для реального времени, clock_gettime syscall удивительно хорош под Linux.)

Я бы просто написал rdtsc внутри оператора asm, который отлично работает для меня и более удобочитаем, чем какой-то непонятный шестнадцатеричный код. Предполагая, что это правильный шестнадцатеричный код (и поскольку он не вылетает и не возвращает постоянно увеличивающееся число, похоже, что это так), ваш код хорош.

Если вы хотите измерить количество тиков, которые принимает фрагмент кода, Вы хотите получить разницу тиков , Вам просто нужно вычесть два значения постоянно увеличивающегося счетчика. Что-то вроде uint64_t t0 = rdtsc(); ... uint64_t t1 = rdtsc() - t0;
Обратите внимание, что если необходимы очень точные измерения, изолированные от окружающего кода, то перед вызовом необходимо выполнить сериализацию, то есть приостановить конвейер. rdtsc (или использовать rdtscp, который поддерживается только на новых процессорах). Единственная сериализующая инструкция, которая может использоваться на каждом уровне привилегий, - это cpuid.

В ответ на следующий вопрос в комментарии:

TSC запускается с нуля, когда вы включаете компьютер (и BIOS сбрасывает все счетчики на всех процессорах на одно и то же значение, хотя некоторые BIOS несколько лет назад не делали этого надежно).

Таким образом, с точки зрения вашей программы, счетчик начал "некоторое неизвестное время в прошлом", и оно всегда увеличивается с каждым тиком часов, который видит процессор. Поэтому, если вы выполняете инструкцию, возвращающую этот счетчик сейчас и в любое время позже в другом процессе, он вернет большее значение (если только процессор не был приостановлен или выключен в промежутке). Разные запуски одной и той же программы получают большие числа, потому что счетчик продолжает расти. Всегда.

Теперь, clock_gettime(CLOCK_PROCESS_CPUTIME_ID) - это другое дело. Это время процессора, которое ОС дала процессу. Это начинается на нуле, когда начинается ваш процесс. Новый процесс тоже начинается с нуля. Таким образом, два процесса, идущие друг за другом, получат очень похожие или идентичные числа, никогда не растущие.

clock_gettime(CLOCK_MONOTONIC_RAW) ближе к тому, как работает RDTSC (и на некоторых более старых системах реализуется с ним). Он возвращает значение, которое постоянно увеличивается. В настоящее время это, как правило, HPET. Однако это действительно время , а не тики. Если ваш компьютер переходит в состояние низкого энергопотребления (например, работает на 1/2 нормальной частота), он будет все еще продвигаться в том же темпе.

Существует много запутанной и / или неправильной информации о TSC, поэтому я решил попытаться прояснить некоторые из них.

Когда Intel впервые представила TSC (в оригинальных процессорах Pentium), было четко задокументировано, что нужно считать циклы (а не время). Однако в то время процессоры в основном работали с фиксированной частотой, поэтому некоторые люди игнорировали документированное поведение и вместо этого использовали его для измерения времени (в частности, разработчики ядра Linux). Их код взломали более поздние процессоры, которые не работают на фиксированной скорости. частота (из-за управления питанием и т. д.). Примерно в то же время другие производители процессоров (AMD, Cyrix, Transmeta и т. д.) были сбиты с толку, и некоторые реализовали TSC для измерения циклов, а некоторые реализовали его, чтобы он измерял время, а некоторые сделали его настраиваемым (via. СПП).

Затем" мультичиповые " системы стали более распространенными для серверов, а еще позже было введено многоядерное ядро. Это привело к незначительным различиям между значениями TSC на разных ядрах (из-за разного времени запуска); но что более важно, это кроме того, это привело к значительным различиям между значениями TSC на разных процессорах, вызванным работой процессоров на разных скоростях (из-за управления питанием и/или других факторов).

Люди, которые пытались использовать его неправильно с самого начала (люди, которые использовали его для измерения времени, а не циклов), много жаловались, и в конечном итоге убедили производителей процессоров стандартизировать использование TSC для измерения времени, а не циклов.

Конечно, это был беспорядок-например, требуется много кода, чтобы определить, что такое TSC фактически измеряет, если вы поддерживаете все процессоры 80x86; и различные технологии управления питанием (включая такие вещи, как SpeedStep, а также такие вещи, как состояния сна) могут по-разному влиять на TSC на разных процессорах; поэтому AMD ввела флаг "TSC инвариант" в CPUID, чтобы сообщить ОС, что TSC можно использовать для правильного измерения времени.

Все последние процессоры Intel и AMD были такими уже некоторое время - TSC считает время и вообще не измеряет циклы. Это означает, если вы хотите измерить циклы, которые вы должны были использовать (специфичные для модели) счетчики мониторинга производительности. К сожалению, счетчики мониторинга производительности представляют собой еще худший беспорядок (из-за их специфики модели и запутанной конфигурации).

Уже хорошие ответы, и Деймон уже упоминал об этом в своем ответе, но я добавлю это из фактического руководства x86 (том 2, 4-301) для RDTSC:

Загружает текущее значение счетчика временных меток процессора (64-разрядный MSR) в регистры EDX:EAX. Регистр EDX загружен 32 битами высокого порядка MSR, а регистр EAX загружен 32 битами низкого порядка. (На процессорах, поддерживающих архитектуру Intel 64, 32 бита высокого порядка каждый из РАКС и гексогена очищены.)

Процессор монотонно увеличивает счетчик времени MSR каждый такт и сбрасывает его на 0 всякий раз, когда процессор сбрасывается.См. "счетчик временных меток" в главе 17 руководства разработчика программного обеспечения для архитектур Intel® 64 и IA-32, том 3B , для получения конкретных сведений о поведении счетчика временных меток.