Лекция 4. Архитектура микропроцессоров
суперскалярность, многоядерность, конвейеризация, закон гроша, vliw, sse, mmx, itanium, ia-64, hyper-threading, epic, 3dnow!
- SSE (англ. Streaming SIMD Extensions, потоковое SIMD-расширение процессора) — это SIMD набор инструкций.
- MMX (Multimedia Extensions—мультимедийные расширения) — коммерческое название дополнительного набора инструкций, выполняющих характерные для процессов кодирования/декодирования потоковых аудио/видео данных действия за одну машинную инструкцию.
- 3DNow! — дополнительное расширение MMX для процессоров AMD, начиная с AMD K6 3D.
- Суперскалярность — архитектура вычислительного ядра, использующая несколько декодеров команд, которые могут нагружать работой множество исполнительных блоков. Планирование исполнения потока команд является динамическим и осуществляется самим вычислительным ядром.
- Hyper-threading (англ. Hyper-threading — гиперпоточность). Торговая марка компании Intel для реализации технологии «одновременной мультипоточности».
Hyper-Threading – это название технологии, существовавшей и ранее вне x86 мира, технологии одновременной многопоточности (Simultaneous Multi-Threading, SMT). Идея этой технологии проста. Один физический процессор представляется операционной системе как два логических процессора, и операционная система не видит разницы между одним SMT процессором или двумя обычными процессорами. В обоих случаях операционная система направляет потоки как на двухпроцессорную систему. Далее все вопросы решаются на аппаратном уровне.
В процессоре с Hyper-Threading каждый логический процессор имеет свой собственный набор регистров (включая и отдельный счетчик команд), а чтобы не усложнять технологию, в ней не реализуется одновременное выполнение инструкций выборки/декодирования в двух потоках. То есть такие инструкции выполняются поочередно. Параллельно же выполняются лишь обычные команды.
- Многоядерность — наличие двух и более вычислительных ядер на одном процессорном кристалле или в одном корпусе в центральном процессоре.
Во всех существующих на сегодняшний день многоядерных процессорах кэш-память первого уровня у каждого ядра своя, а кэш 2-го уровня существует в нескольких вариантах:
- разделяемый — кэш расположен на одном с обоими ядрами кристалле и доступен каждому из них в полном объёме. Используется в процессорах семейств Intel Core.
- индивидуальный — отдельные кэши равного объёма, интегрированные в каждое из ядер. Обмен данными из кэшей L2 между ядрами осуществляется через контроллер памяти — интегрированный (Athlon 64 X2) или внешний (Pentium D).
- Конвейеризация (или конвейерная обработка) в общем случае основана на разделении подлежащей исполнению функции на более мелкие части, называемые ступенями, и выделении для каждой из них отдельного блока аппаратуры. Так обработку любой машинной команды можно разделить на несколько этапов (несколько ступеней), организовав передачу данных от одного этапа к следующему. При этом конвейерную обработку можно использовать для совмещения этапов выполнения разных команд.
Производительность при этом возрастает благодаря тому, что одновременно на различных ступенях конвейера выполняются несколько команд. Конвейерная обработка такого рода широко применяется во всех современных быстродействующих процессорах.
Примеры конвейерной обработки: выполнение циклов команд, арифметика чисел с плавающей точкой, циклы.
Выполнение типичной команды можно разделить на следующие этапы:
- Выборка команды - IF (по адресу, заданному счетчиком команд, из памяти извлекается команда);
- Декодирование команды / выборка операндов из регистров - ID;
- Выполнение операции / вычисление эффективного адреса памяти - EX;
- Обращение к памяти - MEM;
- Запоминание результата - WB.
Арифметика чисел с плавающей запятой требует ряд однотипных операций (регламентировано стандартом IEEE 754):
- Вызов операндов
- Выравнивание экспоненты
- Операция
- Нормализация результата
- Предсказание ветвлений
В любой более-менее сложной программе присутствуют команды условного перехода: «Если некое условие истинно — перейти к исполнению одного участка кода, если нет — другого». До тех пор, пока не станет известно, какой участок кода после условного перехода окажется «актуальным» — его невозможно начать декодировать и исполнять (см. внеочередное исполнение). Для того чтобы как-то примирить концепцию внеочередного исполнения с командами условного перехода, предназначается специальный блок: блок предсказания ветвлений.
Он пытается предсказать, на какой участок кода укажет команда условного перехода, ещё до того, как она будет исполнена. Процессором производятся вполне реальные действия: предсказанный участок кода загружается в кэш (если он там отсутствует), и даже начинается декодирование и выполнение его команд. Причём среди выполняемых команд также могут содержаться инструкции условного перехода, и их результаты тоже предсказываются, что порождает целую цепочку из пока не проверенных предсказаний! Разумеется, если блок предсказания ветвлений ошибся, вся проделанная в соответствии с его предсказаниями работа просто аннулируется.
Алгоритмы, по которым работает блок предсказания ветвлений, преимущественно просты. Чаще всего команда условного перехода встречается в циклах: некий счётчик принимает значение X, и после каждого прохождения цикла значение счётчика уменьшается на единицу. Соответственно, до тех пор, пока значение счётчика больше нуля — осуществляется переход на начало цикла, а после того, как он становится равным нулю — исполнение продолжается дальше. Блок предсказания ветвлений просто анализирует результат выполнения команды условного перехода, и считает, что если N раз подряд результатом стал переход на определённый адрес — то и в N+1 случае будет осуществлён переход туда же. Однако, несмотря на весь примитивизм, данная схема работает просто замечательно: например, в случае, если счётчик принимает значение 100, а «порог срабатывания» предсказателя ветвлений (N) равен двум переходам подряд на один и тот же адрес — легко заметить, что 97 переходов из 98 будут предсказаны правильно!
Разумеется, несмотря на достаточно высокую эффективность простых алгоритмов, механизмы предсказания ветвлений в современных CPU всё равно постоянно совершенствуются и усложняются — но тут уже речь идёт о борьбе за единицы процентов: например, за то, чтобы повысить эффективность работы блока предсказания ветвлений с 95 процентов до 97, или даже с 97% до 99...
- Предвыборка данных
Блок предвыборки данных (Prefetch) очень похож по принципу своего действия на блок предсказания ветвлений — с той только разницей, что в данном случае речь идёт не о коде, а о данных. Общий принцип действия такой же: если встроенная схема анализа доступа к данным в ОЗУ решает, что к некоему участку памяти, ещё не загруженному в кэш, скоро будет осуществлён доступ — она даёт команду на загрузку данного участка памяти в кэш ещё до того, как он понадобится исполняемой программе. Результативно работающий блок предвыборки позволяет существенно сократить время доступа к нужным данным, и, соответственно, повысить скорость исполнения программы. К слову: грамотный Prefetch очень хорошо компенсирует высокую латентность подсистемы памяти, подгружая нужные данные в кэш, и тем самым, нивелируя задержки при доступе к ним, если бы они находились не в кэше, а в основном ОЗУ.
Однако, разумеется, в случае ошибки блока предвыборки данных, неизбежны негативные последствия: загружая де-факто «ненужные» данные в кэш, Prefetch вытесняет из него другие (быть может, как раз нужные). Кроме того, за счёт «предвосхищения» операции считывания, создаётся дополнительная нагрузка на контроллер памяти (де-факто, в случае ошибки — совершенно бесполезная).
Алгоритмы Prefetch, как и алгоритмы блока предсказания ветвлений, тоже не блещут интеллектуальностью: как правило, данный блок стремится отследить, не считывается ли информация из памяти с определённым «шагом» (по адресам), и на основании этого анализа пытается предсказать, с какого адреса будут считываться данные в процессе дальнейшей работы программы. Впрочем, как и в случае с блоком предсказания ветвлений, простота алгоритма вовсе не означает низкую эффективность: в среднем, блок предвыборки данных чаще «попадает», чем ошибается (и это, как и в предыдущем случае, прежде всего связано с тем, что «массированное» чтение данных из памяти, как правило происходит в процессе исполнения различных циклов).