Использует ли Intel пример времени чтения кода asm с использованием еще двух регистров, чем необходимо?

Я изучаю измерение производительности с помощью регистра меток времени (TSR) в процессорах x86. Это полезный регистр, поскольку он измеряет монотонную единицу времени, которая не зависит от изменения тактовой частоты. Очень круто.

Вот документ Intel, показывающий фрагменты asm для надежного тестирования производительности с использованием TSR, включая использование cpuid для конвейерной синхронизации. Смотрите страницу 16:

http://www.intel.com/content/www/us/en/embedded/training/ia-32-ia-64-benchmark-code-execution-paper.html

Чтобы прочитать время начала, он говорит (я немного аннотировал):

__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, заставляет меня сомневаться в достоверности этой статьи.

Другие вопросы по тегам