Поскольку современный процессор использует тяжелый конвейер даже для ALU, несколько выполнений независимых арифметических операций могут выполняться за один цикл, например, четыре операции добавления могут выполняться за 4 цикла, а не 4 * задержки одного добавления.
Даже при наличии конвейеров и наличии конфликтов на портах выполнения я хотел бы реализовать задержки с точностью до цикла, выполняя некоторые инструкции таким образом, чтобы время выполнения последовательности инструкций было предсказуемым. Например, если инструкция x занимает 2 цикла и не может быть обработана конвейером, то, выполняя x четыре раза, я ожидаю, что смогу установить задержки на 8 циклов.
Я знаю, что это обычно невозможно для пользовательского пространства, потому что ядро может вмешиваться между последовательностями выполнения и может привести к большей задержке, чем ожидалось. Однако я предполагаю, что этот код выполняется на стороне ядра без прерываний или изолированного ядра, свободного от шума.
Взглянув на https://agner.org/optimize/instruction_tables.pdf, я обнаружил, что инструкция CDQ не требует операции с памятью и занимает 1 цикл в своей задержке и обратной пропускной способности. Если я правильно понимаю, это означает, что если нет конкуренции за порт, используемый CDQ, он может выполнять эту инструкцию в каждом цикле. Чтобы проверить это, я помещаю CDQ между таймером RDTSC и устанавливаю частоту ядра как номинальную частоту ядра (в надежде, что она такая же, как цикл TSC). Также я прикрепил два процесса к гиперпоточным ядрам; один попадает в цикл while (1), а другой выполняет инструкцию CDQ. Кажется, что добавление одной инструкции увеличивает 1-2 цикла TSC.
Однако меня беспокоит случай, когда требуется много инструкций CDQ для установки больших задержек, таких как 10000, что может потребовать как минимум 5000 инструкций. Если размер кода слишком велик для размещения в кеше инструкций и вызывает промах в кеше и промах TLB, это может вызвать некоторые колебания в моей задержке. Я попытался использовать простой цикл for для выполнения инструкций CDQ, но не могу гарантировать, можно ли использовать цикл for (реализованный с помощью jnz, cmp и sub), потому что он также может внести некоторый неожиданный шум в мою задержку. Может ли кто-нибудь подтвердить, могу ли я использовать инструкцию CDQ таким образом?
Добавлен вопрос
После тестирования с несколькими инструкциями CMC кажется, что 10 инструкций CMC добавляют 10 циклов TSC. Я использовал приведенный ниже код для измерения времени выполнения 0, 10, 20, 30, 40, 50
asm volatile(
"lfence\t\n"
"rdtsc\t\n"
"lfence\t\n"
"mov %%eax, %%esi\t\n"
"cmc\n\t" // CMC * 10, 20, 30, 40, ...
"rdtscp\n\t"
"lfence\t\n"
"sub %%esi, %%eax\t\n"
:"=a"(*res)
:
: "ecx","edx","esi", "r11"
);
printf("elapsed time:%d\n", *res);
Я получил 44-46, 50-52, 62-64, 70-72, 80-82, 90-92 для (без CMC, 10CMC, 20CMC, 30CMC, 40CMC, 50CMC). Когда результаты RDTSC варьируются от 0 до 2 циклов TSC при каждом выполнении, кажется, что инструкция 1CMC соответствует задержке в 1 цикл. За исключением первого раза добавления 10 CMC (он увеличивается не на 10, а на 6 ~ 8), большую часть времени добавление еще 10 инструкций CMC добавляет (10 + -2) больше цилиндров TSC. Однако, когда я изменил CMC на инструкцию CDQ, как я изначально использовал в вопросе, кажется, что 1 инструкция CDQ не соответствует 1 циклу в машине i9900K. Однако, когда я смотрю на таблицу оптимизации agner, кажется, что инструкции CMC и CDQ на самом деле не отличаются. Связано ли это с тем, что инструкции CMC друг за другом не зависят друг от друга, а инструкции CDQ имеют зависимость между ними?
Также, если мы считаем, что переменная задержка была вызвана rdtsc, а не из-за прерывания или других проблем конкуренции ... тогда кажется, что инструкция CMC может использоваться для задержки 1 основного цикла, верно? Потому что я привязал свое ядро к работе с тактовой частотой 3,6 ГГц, которая предполагалась тактовой частотой TSC на i9900k .. Я просмотрел упомянутый вопрос, но не могу уловить точных деталей ..
cdq
в Zen 2 имеет пропускную способность 4 за такт, по сравнению с явно 1 за такт на Skylake (измерено), хотя вы ожидаете, что он будет работать 2 за такт, исходя из возможности работать на порте 0 или порту 6. Может, он имеет выходную зависимость от Intel? В любом случае, это тактовые циклы ядра, а не фиксированная частота. А выполнение вне очереди означает, что эти задержки имеют значение только в течение длинных интервалов, значительно превышающих размер ROB (буфер переупорядочения), 224 мопов на Skylake или по крайней мере 97 мопов RS. В этот момент он достаточно длинный, чтобы прерывание могло вызвать гораздо более длительную задержку. - person Peter Cordes   schedule 22.09.2020cmc
(переключение CF) может помочь. Но это вряд ли полезно для вашей общей цели цикла задержки. Возможно, в сочетании сlfence
, но это может задержать намного дольше, чем вы хотите, в зависимости от того, сколько времени займет выполнение существующих инструкций в полете. например загрузка из-за промаха кэша. - person Peter Cordes   schedule 22.09.2020times 10 cdq
/dec ebp
/jnz
в статическом исполняемом файле, который я синхронизировал с помощью счетчиков производительности HW, поэтому мне не приходилось полагаться на RDTSC. - person Peter Cordes   schedule 23.09.2020