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

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

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

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

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

Ответ 1

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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