Тема 5

Модели на паметта в инструкциите


страницата се нуждае от дописване/преглеждане


"Модел на паметта" за нашите цели ще рече, "какво се съдържа в полетата за адрес на операндите?".
Всъщност говорим за две доста подобни, но различни понятия. Дотолкова подобни, че вероятно надолу тук-там са поомешани термините, отнасящи се до тях. Едното е видът на инструкциите, а другото е архитектурата на процесора. Двете са взаимно свързани, тъй като определен тип инструкции се изпълняват върху предвиден за този тип инструкции хардуеър.
В изложението, когато става дума за инструкцията, ще говорим за модел, а когато стане дума за реализиращия я хардуеър - за архитектура.

Да припомним формата на инструкция с деструктивно записване (записването на резултата се извършва в първия операнд):
IS_threechunk_format.PNG
Разбира се, има варианти на инструкции, в които операндите присъстват директно със стойността си, но по обяснени вече съображения няма да ги обсъждаме.

Модел с един акумулатор (Accumulator architecture)

one_accumlator_model_memoryadressing.PNG

Моделът е използван някога в първите микропроцесори. За да се постигне краткост на инструкцията, се решава да се изпусне полето Aо1, а вместо него да се подразбира, че първият операнд е Acc - "акумулаторен регистър по премълчаване", поставен на единия вход на АЛУ. Така мястото на операнда се знае винаги. Инструкциите от този вид се наричали "акумулаторни", или "с подразбиране".
Както виждате, вместо инструкцията да има два аргумента, единият винаги е Acc по подразбиране и затова няма втори адрес в инструкцията.

В мнемоничен код извършването на едно събиране би изглеждала така:

load A     ; от адрес A се прочита стойност, която неявно се вкарва в регистъра Acc
add B      ; от адрес B се прочита стойност и се добавя към Acc
store C    ; съдържанието на Аcc се записва в клетка с адрес C

Получават се еднооперандни инструкции (с втори операнд по премълчаване).

В най-старите микропроцесори на Intel акумулаторният регистър се наричал просто А.
Недостатък е трудното по-нататъшно оптимизиране на работата на регистъра, понеже той само се зарежда и изпразва.

Модел с вътрешен стек (Stack architecture)

Следващата идея е използването, вместо на регистър, на стек, с който ЦП непрекъснато да работи вътрешно. Инструкциите отново са еднооперандни, подобно на акумулаторния модел, но за разлика от него, тук могат да бъдат запаметени толкова променливи, колкото позволява максималният размер на стека. Всяка променлива, както и междинните резултати, се запомнят във върха на стека. В тази връзка е важно в стека да не остава "боклук".

Събиране в мнемоничен код:

push A     ; Stack[++Top] = M[A] (адрес A от паметта)
push B     ; Stack[++Top] = M[B]
add        ; T1 = Stack[Top--] ; T2 = Stack[Top--]; Stack[++Top] = T1 + T2
pop C    ; M[C] = Stack[Top--]

T1 и T2 са вътрешни регистри. Да, наистина има едновременно и стек, и регистри - но регистрите са малък краен брой и не се адресират.

Същата процедура с адресируем регистър:

load R1, A     ; R1 = M[A]
add R1, B      ; R1 += M[B]
store C, R1    ; M[C] = R1

Модел с разширен акумулатор (Register - Memory architecture)

Идва ред и на модела с разширен акумулатор (в Intel-ските процесори, които са били базирани на този модел, самият регистър е бил наречен AX - Accumulator eXtended). В този модел вместо единичен регистър, на единия вход стоят малък брой регистри с къси имена (например 16 регистъра с 4-битови имена). Идеята се оказва много удобна, моделът - сравнително ясен, инструкцията - компромис между дължината на инструкциите и компактността на кода (броя инструкции, необходими за дадено действие), самият код може да се оптимизира дотолкова, че статистически за повечето действия се използват средно две инструкции, в сравнение със средно три за модела с две адресни полета, и като цяло, всичко изглежда прекрасно.

Недостатъкът все още е, че инструкцията остава асиметрична - единият операнд се зарежда бързо от регистъра, докато за втория се изчаква да се извлече от паметта, което отнема поне 10 процесорни такта, и на операнда - жална му майка1. Изчакването е толкова голямо, понеже паметта не е синхронна с процесора - ако един процесорен цикъл отнема, да речем, 50$ns$, то достъпът до паметта отнема поне 500. Въпреки това, моделът е удобен за ръчно програмиране.

Модел M-M / S-S (Memory - Memory / Storage - Storage architecture)

Измислен е и модел memory - memory - "памет-памет". При него напълно се елиминират междинните акумулатори, стекове и други туморни образувания. В този случай инструкциите, въпреки, че са дълги, водят до къс код (код с голяма плътност).
Като пример ще вземем инструкцията за събиране, изразена в мнемоничен код:

add C, A, B    ; M[C] = M[A] + M[B]

Та, защо е плътен кодът? Ами тук имаме 1B (8b) код на операция и 3 адреса от паметта. При модела R-M (с разширен регистър) за същата работа използваме 3 инструкции, всяка с по 1B код на операция, 4b адрес (име) на регистър и по един адрес от паметта. Получаваме: 8b служебна част при S-S, сравнено с 36b при R-R, и еднакъв брой адреси от паметта при двата модела.
Същевременно, знаем, че обръщенията към паметта са бавни - а тук имаме точно 3 такива. Затова се получава, че при този модел кодът е най-компактен и най-бавен.
Все пак, моделът намира приложение там, където е налична малко памет за съхранение на кода.

Модел R-R (Load - Store architecture)

Следващият писък на модата е моделът register - register ("регистър - регистър") - с 2 или повече акумулаторни регистъра. Отсега казваме, че при него кодът ще бъде най-дълъг от всичко, разгледано досега. Но за сметка на това е симетричен по отношение на операндите, и операции като събирането могат да се извършват с максимална скорост (до 1 цикъл, ако регистрите са двустъпални).
Събиране в мнемоничен код:

load R1, A
load R2, B
add R1, R2
store C, R1

Изглежда тежко, но още не сме казали най-хубавото относно R-R: поради големия брой регистри може добре да се оптимизира. Да погледнем подобен код, но в по-голям мащаб:

load R1, A
load R2, B
load R3, D
add R1, R2
add R1, R3
...

Забележете следното: третият load и следващият го add нямат общи операнди. Освен това load адресира паметта и е бавна, докато add е бърза. От това следва, че те могат да се изпълнят паралелно - докато се зарежда D в R3, R1 и R2 се събират. Gentlemen, we have parallelism!
Това е един от принципите на RISC : инструкциите за зареждане са от типа R-M, а операциите се реализират чрез R-R инструкции. Така една оптимизирана програма може да зарежда данни в началото на изпълнението си и да записва в края, а средата да бъде бърза за изпълнение, обръщаща се само към блока регистри. Такава програма може да има "много благоприятно влияние" върху конвейера на процесора. Инструкциите са симетрични, компактни, детерминирани (знае се колко ще продължат).

Недостатък (не може без него) е, че моделът R-R е контекстно зависим. Стремежът е програмата да взаимодейства с паметта само в началото и края си. Обаче, ако се наложи да се прекъсне - например, извикване на подпрограма - се налага контекстът (състоянието на регистрите) да се запише в паметта, за да се освободят те за ново изпълнение.
Друг недостатък е, че максималният размер на операндите, които можем да съхраним в регистъра, е равен на размера на регистрите (не може да се съхрани операнд, по-голям от регистъра, в регистъра, нали ?). Наред с другите неудобства, това означава, че дълги конструкции, като структури и знакови низове, не могат да се обработват от R-R инструкции.
Трети недостатък (не толкова силно изразен) е, че операнди, съхранявани в регистри, не могат да се обработват чрез указатели.

В края на краищата…

… коя е най-добрият модел?
Не е съвсем еднозначно, но фактите говорят - в днешно време най-широко се използва Load - Store архитектура, върху която се изпълняват предимно R-R и R-M инструкции. Бързодействието взима връх над дължината на кода и простотата на устройството. Е, има и изключения.

В архитектурата на Intel IA има 8 акумулаторни регистъра (3 бита за идентифициране на регистър). В по-новите RISC архитектури имаме 32 регистъра (5 бита). Фамилията Itanium2 има 128 акумулаторни регистъра, като Itanium се счита за истински RISC.
Има стремеж да се увеличава броят регистри за съхранение на повече междинни резултати и по-малко обръщения към паметта.

Недостатъкът, пораждан от това, е (освен повечето битове в инструкцията, указващи регистър), че блокът регистри започва да действа по-бавно. При 128 регистъра например е нужен един такт в повече, за да влязат данните в АЛУ. Още по-неприятното е, че се забавя смяната на контекста.

Допълнителни четива

http://en.wikipedia.org/wiki/Instruction_set#Number_of_operands
Това препоръчвам на всеки, който не е разбрал нещо от написаното по-горе.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License