"Предположим, что загрузка и сохранение значений в памяти стоят 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, то вышеприведенное не имеет значения.