Как рассчитать тактовые циклы при программировании сборки mips? - PullRequest
0 голосов
/ 19 марта 2019

Я искал везде, и я собрал конвейеры или что-то в этом роде. Я проверил другие программы, и кажется, что это одноцикловые и многоцикловые: Тактовые циклы одноцикловых и многоцикловых MIPS

Как узнать разницу для какого цикла.

как например, сколько тактов это будет:

li $a0, 0 # $a0 = 0
 li $t0, 10 # Initialize loop counter to 10
Loop:
 add $a0, $a0, $t0
 addi $t0, $t0, -1 # Decrement loop counter
 bgt $t0, $zero, Loop # If ($t0 > 0) Branch to loop

Мой профессор дал мне это для задания: «Предположим, что загрузка и сохранение значений в памяти стоят 100 циклов плюс стоимость инструкции сам. "

Из того, что я прочитал, загрузка составляет 5 циклов, так почему мой профессор говорит, что это 100 циклов? Могу ли я сделать это любое число, которое я хочу? Я очень смущен.

Ответы [ 3 ]

3 голосов
/ 19 марта 2019

Этот вопрос не имеет смысла.

Стандартный многоцикловый конвейер RISC, используемый в большинстве образовательных реализаций MIPS, в основном основан на требовании одновременного доступа к программам и памяти данных водин цикл.Чтобы «предположить, что загрузка и хранение значений в памяти стоят 100 циклов», потребуется совершенно другая архитектура.

2 голосов
/ 19 марта 2019

Мы должны различать два случая:

Случай 1: симуляторы MIPS

Из того, что я прочитал, загрузка составляет 5 циклов, поэтомупочему мой профессор говорит, что его 100 циклов.

Вы работаете не с реальным процессором, а только с имитированным.Поэтому рассуждать о том, сколько циклов «действительно» нужно вашей программе на имитируемом процессоре, не имеет смысла.

Может быть имитатор имитирует 5 циклов для каждого обращения к памяти.Однако другой симулятор может имитировать 10 циклов или только 1 цикл для доступа к памяти.

Это означает, что вам нужно будет сказать , какой имитатор используется, когда говорите о количестве симулируемых циклов,И ваш профессор говорит, что должен быть использован симулятор, имитирующий 100 циклов.

Случай 2: Реальные процессоры MIPS

Могу ли я сделать это как угодно?

В этом случае вам придется проверить руководство по ЦПУ, чтобы получить реальное количество циклов, в которых нуждаются ЦП.

Однако набор инструкций реального типа MIPSПроцессоры не на 100% идентичны эмуляторам MIPS.В вашей программе инструкция bgt будет работать по-другому.

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

Если вы хотите знать, правдоподобно ли число 100 при использовании реального CPU:

Как мы знаем из "Спектра""и" нарушения безопасности ", время, необходимое для чтения памяти на реальных процессорах, составляет массово в зависимости от состояния кэшей процессора.Если мы предположим, что a0 указывает на периферийное устройство с отображением в памяти (которое никогда не кэшируется), возможно 100 циклов.

1 голос
/ 19 марта 2019

"Предположим, что загрузка и сохранение значений в памяти стоят 100 циклов плюс стоимость самой инструкции."

Это не имеет особого смысла.Если вы не предполагаете, что выборка инструкций также слишком медленная, это все равно, что иметь кэш инструкций, но не кэш данных.

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

Как указывает @Martin, вы можете составить любое число и смоделировать его соответствующим образом, даже если нет убедительной инженерной причины, по которой ЦП будет построен таким образом.

Если выпытаясь смоделировать производительность эмулятора, такого как сам MARS, на центральном процессоре, все еще не очень вероятно, что загрузка / хранение также будет такой дорогой.Расходы интерпретатора на инструкцию сильно различаются в зависимости от того, насколько хорошо работает прогнозирование ветвлений на центральном процессоре хоста, а эмуляция гостевой памяти - небольшая часть того, что делает эмулятор интерпретации.


На современных x86 нагрузках обычно используется 5 циклов. задержка для попадания в кэш L1d (от адреса готовности до готовности данных), но они также имеют пропускную способность 2 на такт.Таким образом, даже без каких-либо промахов кэша до 10 загрузок могут быть в полете одновременно только в двух конвейерных модулях исполнения нагрузки процессора Intel Sandybridge-семейства или AMD K8 / Bulldozer / Zen.(С помощью буферов нагрузки для отслеживания нагрузок, связанных с отсутствием кэша, во всем бэкэнде не по порядку может выполняться гораздо большее количество загрузок одновременно.)

Нельзя сказать, что нагрузка«стоит» 5 циклов на таком процессоре, если только вы не говорите об определенном контексте окружающего кода , например, обходит связанный список, где вы сталкиваетесь с задержкой загрузки, потому что адрес для следующей загрузки зависит от результата текущейload.


При обходе массива вы обычно генерируете следующий указатель с инструкцией add (или MIPS addui), которая имеет задержку в 1 цикл.Даже если нагрузки имели 5 циклов задержки на простом конвейере заказа, развертывание + программная конвейеризация позволят вам выдержать 1 нагрузку за такт.

В конвейерном процессоре производительность не одномерна,Вы не можете просто указать номера затрат в инструкциях и добавить их .Это можно увидеть даже для инструкций ALU на классических MIPS по порядку с инструкцией умножения: если вы не используете mflo / mhi сразу, задержка умножения скрывается за счет любых инструкций, которыми вы заполняете этот пробел.


Как указывает @duskwuff, a классический 5-этапный конвейер RISC (выборка / декодирование / exec / mem / write-back), как MIPS первого поколения предполагает кешХиты имеют пропускную способность памяти 1 / такт и задержку для доступа к самому L1d .Но на этапе MEM остается место для задержки в 2 цикла для нагрузок (включая генерацию адреса на этапе EX).

И я полагаю, что они также не нуждались в буфере хранения.Более сложные процессоры используют буфер хранилища, чтобы отделить выполнение от хранилищ, которые могут потенциально отсутствовать в кеше L1d, скрывая задержку хранилища даже при пропадании кеша.Это очень хорошо, но не требуется .

В ранних процессорах часто использовались простые виртуально адресуемые кэши с прямым отображением, что обеспечивало такую ​​низкую задержку кэша без снижения максимальной тактовой частоты.Но при промахе кеша трубопровод останавливается.https://en.wikipedia.org/wiki/Classic_RISC_pipeline#Cache_miss_handling.

Более сложные процессоры порядка могут загружать табло вместо того, чтобы останавливаться, если какой-либо из них отсутствует в кэше, и останавливаться, только если более поздняя инструкция пытается фактически прочитать регистр, который был последний раз записан загрузкой, котораяеще не завершено.Это позволяет осуществлять попадание под промах и несколько незавершенных кешей, чтобы создать параллелизм памяти, позволяя одновременно осуществлять несколько обращений к 100-тактовой памяти.


Но, к счастью для вас, вашцикл не включает в себя доступ к памяти. Это чистая ветвь ALU +.

На реальном MIPS со слотами задержки ветвления вы можете написать это так:

 li   $t0, 10                        # loop counter
 li   $a0, 10                        # total = counter    # peel the first iteration

Loop:                                # do{
 addi $t0, $t0, -1
 bgtz $t0, Loop                      # } while($t0 > 0);
 add $a0, $a0, $t0                   # branch-delay: always executed for taken or not

Это всего лишь 10 + 9 + 8 + ... + 0 = (10 * 11) / 2, что было бы лучше сделать с умножением вместо цикла.Но дело не в этом, мы анализируем цикл.Мы делаем то же самое количество добавлений, но мы делаем += 0 в конце вместо 0 + 10 в начале.

Обратите внимание, что я использовал настоящую инструкцию MIPS bgtz ,не псевдоинструкция bgt с $zero.Надеемся, что ассемблер выберет это для особого случая $zero, но он может просто следовать обычной схеме использования slt $at, $zero, $t0 / bne $at, $zero, target.

Классический MIPS не выполняет предсказание ветвлений + спекулятивное выполнение(он имеет слот задержки ветвления, чтобы вместо этого скрыть пузырь от управляющей зависимости).Но чтобы это работало, необходимо, чтобы вход ветви был готов на этапе идентификации , поэтому чтение результата предыдущего add (который дает результат в конце EX) вызовет остановку на 1 цикл,(Или хуже в зависимости от того, поддерживается ли переадресация на стадию ID. https://courses.engr.illinois.edu/cs232/sp2009/exams/final/2002-spring-final-sol.pdf вопрос 2 В части (а) приведен пример, подобный этому, но я думаю, что они недооценивают циклы задержки, если вам нужно ждать добавления WBзавершить до запуска bne / bgtz ID.)

Так или иначе, это должно выполняться в лучшем случае 1 итерация на 4 цикла в скалярном порядке MIPS I, который может пересылать из EX вЯ БЫ.3 инструкции + 1 цикл остановки перед каждым bgtz.


Мы можем оптимизировать его, поместив add $a0, $a0, $t0 между счетчиком цикла и ответвлением, заполняя этот цикл остановки полезной работой.

 li    $a0, 10                        # total = counter    # peel the first iteration
 li    $t0, 10-1                      # loop counter, with first -- peeled

Loop:                                 # do{
                                          # counter--   (in the branch delay slot and peeled before first iteration)
 add   $a0, $a0, $t0                      # total += counter
 bgtz  $t0, Loop                      # } while($t0 > 0);
 addi  $t0, $t0, -1

Это выполняется с 3 циклами / итерацией, для 3 инструкций без циклов остановки (снова при условии перенаправления с EX на ID).

Помещение counter-- вСлот задержки перехода помещает его как можно дальше до следующего следующего выполнения ветви цикла.Простой bne вместо bgtz тоже подойдет;мы знаем, что счетчик цикла начинается со знака плюс и уменьшается на 1 каждую итерацию, поэтому не критично, что мы продолжаем проверять как неотрицательные, так и ненулевые.


У меня нетПредставьте себе, какую модель производительности вы используете. Если это не классический 5-этапный MIPS, то вышеприведенное не имеет значения.

...