Использует ли Intel пример времени чтения кода asm с использованием еще двух регистров, чем необходимо?
Я изучаю измерение производительности с помощью регистра меток времени (TSR) в процессорах x86. Это полезный регистр, поскольку он измеряет монотонную единицу времени, которая не зависит от изменения тактовой частоты. Очень круто.
Вот документ Intel, показывающий фрагменты asm для надежного тестирования производительности с использованием TSR, включая использование cpuid для конвейерной синхронизации. Смотрите страницу 16:
Чтобы прочитать время начала, он говорит (я немного аннотировал):
__asm volatile (
"cpuid\n\t" // writes e[abcd]x
"rdtsc\n\t" // writes edx, eax
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t"
//
:"=r" (cycles_high), "=r" (cycles_low) // outputs
: // inputs
:"%rax", "%rbx", "%rcx", "%rdx"); // clobber
Мне интересно, почему регистры нуля используются для получения значений edx
а также eax
, Почему бы не удалить movs и не прочитать значение TSR прямо из edx
а также eax
? Как это:
__asm volatile(
"cpuid\n\t"
"rdtsc\n\t"
//
: "=d" (cycles_high), "=a" (cycles_low) // outputs
: // inputs
: "%rbx", "%rcx"); // clobber
Делая это, вы сохраняете два регистра, уменьшая вероятность разлива компилятора C.
Я прав? Или эти MOVs как-то стратегические?
(Я согласен, что вам нужны чистые регистры для чтения времени остановки, так как в этом сценарии порядок команд меняется на противоположный: у вас есть rdtscp,..., cpuid. Инструкция cpuid уничтожает результат rdtscp).
Спасибо
2 ответа
Вы правы, пример неуклюжий. Обычно если mov
является первой или последней инструкцией в выражении inline-asm, вы делаете это неправильно и должны были использовать ограничение, чтобы сообщить компилятору, где вы хотите вводить или где вывод.
Смотрите мою коллекцию руководств / ссылок inm asm GNU C и другие ссылки в вики - теге /questions/tagged/inline-assembly. (Вики-тег x86 полон хороших вещей для asm в целом.)
Или для rdtsc
в частности, см. Получить количество циклов ЦП? для __rdtsc()
внутренний и хороший встроенный ассемблер в ответе @Mysticial.
он измеряет в монотонной единице времени, которая не зависит от изменения тактовой частоты.
Да, на процессорах, сделанных за последние 10 лет или около того.
Для профилирования часто более полезно иметь время в тактах ядра, а не время настенных часов, чтобы результаты микробенчмарка не зависели от энергосбережения / турбо. Счетчики производительности могут сделать это и многое другое.
Тем не менее, если вы хотите в реальном времени, rdtsc
это самый дешевый способ получить его.
И повторно: обсуждение в комментариях: да cpuid
есть ли сериализация, убедившись, что rdtsc
и следующие инструкции не могут начать выполняться до тех пор, пока не появится CPUID. Вы можете добавить другой CPUID после RDTSC, но это увеличит накладные расходы на измерения, и я думаю, что это даст почти нулевой выигрыш в точности / точности.
LFENCE является более дешевой альтернативой, которая полезна с RDTSC. Введенная в руководстве ссылка на документацию подтверждает тот факт, что она не позволяет более поздним инструкциям начать выполнение до тех пор, пока она и предыдущие инструкции не будут удалены (из ROB/RS в неработающей части ядра). См. Загружает и хранит ли единственные инструкции, которые переупорядочиваются? и конкретный пример его использования см. в clflush для аннулирования строки кэша с помощью функции C. В отличие от настоящих инструкций сериализации, таких как cpuid
, он не очищает буфер хранилища.
(На последних процессорах AMD без включенной защиты Spectre, lfence
даже не частично сериализуется и работает по 4 в такт согласно тестированию Агнера Фога. Работает ли LFENCE на процессорах AMD?)
Маргарет Блум нашла эту полезную ссылку, которая также подтверждает, что LFENCE сериализует RDTSC в соответствии с SDM Intel, и имеет некоторые другие сведения о том, как выполнять сериализацию вокруг RDTSC.
Нет, похоже, нет веской причины для избыточных инструкций MOV во встроенной сборке. В документе сначала вводится встроенная сборка со следующим утверждением:
asm volatile (
"RDTSC\n\t"
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t": "=r" (cycles_high1), "=r" (cycles_low1));
Это имеет очевидную проблему, заключающуюся в том, что она не сообщает компилятору, что EAX и EDX были изменены инструкцией RDTSC. В статье указывается на эту ошибку и исправляет ее, используя клобберы:
asm volatile ("RDTSC\n\t"
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t": "=r" (cycles_high), "=r" (cycles_low)::
“%eax”, “%edx”)
Никаких других оправданий для написания этого способа не приводится, кроме исправления ошибки в предыдущем примере. Похоже, что автору статьи просто не известно, что ее можно написать более просто:
asm volatile ("RDTSC\n\t"
: "=d" (cycles_high), "=a" (cycles_low));
Точно так же автор, по-видимому, не знает, что существует более простая версия улучшенного оператора asm, которая использует RDTSC в сочетании с CPUID, как вы продемонстрируете в своем посте.
Обратите внимание, что автор статьи постоянно использует термин "IA64" для обозначения 64-битного набора команд и архитектуры x86 (по-разному обозначаемых как x86_64, AMD64 и Intel 64). Архитектура IA-64 - это нечто совершенно иное, оно используется процессорами Intel Itaninum. У него нет регистров EAX или RAX и нет инструкции RDTSC.
Хотя на самом деле не имеет значения, что встроенная сборка авторов сложнее, чем нужно, этот факт в сочетании с неправильным использованием IA64, что должно было быть замечено редакторами Intel, заставляет меня сомневаться в достоверности этой статьи.