Rouge... Roge... Wrong... r... абе нещо-си-Conf 2014
Автор Борислав Станимиров
Няма да се занимаваме с тях.
Ако някой се интересува, "But How Do It Know" е една чудесна книга по въпроса:
Има много начини да се класифирицират:
Например по instruction set:
Или по предназначение:
За тях може би ще си говорим на следващия OpenFest
Микроконтролери, виртуално CPU, екзотики, MISC, и др.
Ужасно много си приличат
С тези разлики:
Фокусираме се върху х86
... и всичко това се случва само в идеалния вариант.
Четене на данни. Wait state. Празни цикли.
Стига cache (благодарете ми с cache, в брой или на ръка)
Всичко това в 5 нива:
1: [F ][D1][D2][EX][WB] 2: [F ][D1][D2][EX][WB] 3: [F ][D1][D2][EX][WB] 4: [F ][D1][D2][EX][WB] 5: [F ][D1][D2][EX][WB] cpu: цик цик цик цик цик цик цик цик цик
Има ги очевидните, наследени от преди: Преджуркване на cache → Wait state → Sad panda
Размяна на стойности
XOR a, b; XOR b, a; XOR a, b;
Как работи това в нашия pipeline?
XOR a,b: [F ][D1][D2][EX][WB] XOR b,a: [F ][D1][PS][PS][D2][EX][WB] XOR a,b: [F ][D1][PS][PS][PS][PS][D2][EX][WB] cpu: цик цик цик цик цик цик цик цик цик цик цик
Stall се получава и когато инструкция трябва да чака
ООО че яко. Out-of-order execution. 486 на стероиди
Хитро взимаме до 32 инструкции от instruction cache
Взимаме до 4 инструкции от fetch buffer
Разталпваме ги на µ-ops - парчета от инструкции
Подреждаме входа в регистри по 4 µ-ops на цикъл
Копираме ги в тайни регистри за паралелизъм
Преподреждаме µ-ops в подходящ ред за изпълнение
ООО Меджик. Изпълняваме µ-ops по ултрамегабърз начин
Заемаме максимално количество портове (execution units)
Изчакваме всички µ-ops за инструкция
Попълваме изхода в подходящия ред
Решихме pipeline stall проблема!
За програмата и програмиста цялата тая магия изглежда не по-различна от дедо 8086
Създадохме нов проблем
... когато имаме branching
... инвестираме прекалено много
Получаваме Pipeline stall с чудовищни размери
Когато имаме сто (или повече) инструкции, заредени в pipeline-a, това е неприемливо
Отговорът на нашия проблем
За програмата светът изглежда така, както е изглеждал в 8086
Но за инструкцията...
Да вземем една инструкция, чиято програма върви
Животът е спокоен
В един момент хитрият prefetch я пъха в instruction cache
Може би на около 1500 инструкции от нея
Instruction pointer наближава
На около 24 инструкции от нея, тя бива пъхната заедно с 5 свои съседи във fetch buffer
Идва и реда да влезе в декодера
Той я разглобява на (примерно) 5 µ-ops
Декодера разбира че тя има нужда от допълнителни данни
На другия край на света, данните се напъхват в data cache
µ-op-овете и влизат в register alias table
За тях се разпределят нужните "фалшиви" регистри
µ-op-овете и влизат в reorder buffer
При първа възможност се пращат в reservation station
Някои от тях веднага се изпълняват. Тя не знае защо
Други от µ-op-овете и чакат, докато най-различни µ-op-ове от други инструкции се изпълняват
... и чакат
... и чакат още
Най сетне даните им са заредени
Те се изпълняват
Вече изцяло изпълнената инструкция се сглобява на излизане от reservation station и резултатите и са готови
На края, в последната фаза, тя се озовава в опашка със съседите си от самото начало, за да напусне процесора
...които важат само за native компилираните програми
...и които може би знаехте преди (но сега знаете защо)
Борислав Станимиров / ibob.github.io / @natcbb
Тази презентация е тук: http://ibob.github.io/slides/cpupbg/
Презентацията е лицензирана с Creative Commons Признание 3.0
Как работи Hyper-Threading?