Получение циклов ЦП с использованием RDTSC - почему значение RDTSC всегда увеличивается?
Я хочу получить циклы процессора в определенной точке. Я использую эту функцию в этой точке:
static __inline__ unsigned long long rdtsc(void)
{
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
}
Проблема в том, что он возвращает всегда увеличивающееся число (при каждом запуске). Как будто это относится к абсолютному времени.
Я неправильно использую функции?
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 для измерения циклов, а некоторые внедрили его так, чтобы он измерял время, а некоторые сделали его настраиваемым (с помощью MSR).
Тогда "многочиповые" системы стали более распространенными для серверов; и даже позже многоядерный был введен. Это привело к незначительным различиям между значениями 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 бита каждого из RAX и RDX очищаются.)
Процессор монотонно увеличивает счетчик меток времени MSR каждый тактовый цикл и сбрасывает его в 0 при каждом сбросе процессора. См. "Счетчик меток времени" в главе 17 Руководства разработчика программного обеспечения для архитектуры Intel® 64 и IA-32, том 3B, для получения подробной информации о поведении счетчика меток времени.