Получение циклов процессора с использованием RDTSC - почему значение RDTSC всегда увеличивается? - PullRequest
15 голосов
/ 22 декабря 2011

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

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

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

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

Ответы [ 3 ]

26 голосов
/ 22 декабря 2011

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

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

(Другая «проблема» заключается в том, что многие люди используют RDTSC для измерения времени, которое не , что он делает, но вы написали, что хотите, чтобы циклы ЦП, так что все в порядке. Если вы * * * * * * * Используйте измерение RDTSC для измерения времени, у вас могут возникнуть сюрпризы, когда энергосбережение или гиперскорость или что-то еще, называемое множеством методов изменения частоты. На самом деле, системный вызов clock_gettime на удивление хорош в 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 нормальной частоты), он будет все еще двигаться с той же скоростью.

19 голосов
/ 22 декабря 2011

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

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

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

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

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

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

1 голос
/ 21 октября 2016

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

Загружает текущее значение счетчика меток времени процессора (64-разрядное MSR) в регистры EDX: EAX. В регистр EDX загружаются старшие 32 бита MSR, а в регистр EAX загружаются младшие 32 бита. (На процессорах, поддерживающих архитектуру Intel 64, старшие 32 бита каждого из RAX и RDX очищаются.)

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

...