Предисловие
В начале сентября компания AMD обещает представить свои новые четырёхъядерные процессоры архитектуры K10. Этими первыми процессорами с новой архитектурой будут серверные чипы Opteron на ядре с кодовым названием Barcelona. К сожалению, инженерам AMD в текущей ревизии не удалось добиться массового производства процессоров, работающих на высоких частотах. Основным препятствием к наращиванию частоты, по-видимому, стал тот факт, что четыре ядра на высоких частотах потребляют мощность, превышающую значение, предусмотренное термопакетом платформы. Потребляемая мощность будет понижаться, а частоты будут повышаться с выпуском новых ревизий и переходом на более тонкие техпроцессы. Пока же AMD необходимо срочно начинать продажи, чтобы выбираться из тяжёлого экономического положения, поэтому поставки Barcelona начнутся с серверных четырёхъядерных процессоров, работающих на частоте 2 ГГц.
В четвёртом квартале 2007 года AMD обещает повысить частоты Opteron до 2,4–2,5 ГГц и выпустить настольные версии процессоров архитектуры K10:
Phenom FX (кодовое имя Agena FX) – 4 ядра, 2 МБ кэша L3, ориентировочные стартовые частоты 2,2–2,4 ГГц, сокеты AM2+ и F+;
Phenom X4 (кодовое имя Agena) – 4 ядра, 2 МБ кэша L3, ориентировочные стартовые частоты 2,2–2,4 ГГц, сокет AM2+.
Позднее, в начале 2008 года, AMD обещает представить упрощённые версии новых процессоров:
Phenom X2 (кодовое имя Kuma) – 2 ядра, 2 МБ кэша L3, ориентировочные стартовые частоты 2,2–2,6 ГГц, сокет AM2+;
Athlon X2 (кодовое имя Rana) – 2 ядра, без кэша L3, ориентировочная стартовая частота 2,2 ГГц, сокет AM2+;
Sempron (кодовое имя Spica) – 1 ядро, ориентировочные стартовые частоты 2,2–2,4 ГГц, сокет AM2+.
Но это всё дело хоть и ближайшего, но будущего, а пока же давайте посмотрим, какие нововведения привнесла новая архитектура AMD. В этой статье я попытаюсь подробно раскрыть её детали и оценить, что мы можем от неё ожидать.
Выборка инструкций
Исполнение кода процессором начинается с выборки инструкций из кэша команд L1I и их декодирования. Инструкции x86 имеют переменную длину, что затрудняет определение их границ перед декодированием. Для того, чтобы определение длины инструкций не влияло на темп декодирования, процессоры K8/K10 выполняют предекодирование команд во время загрузки строк в кэш команд L1I. Информация о разметке команд хранится в кэше L1I в специальных полях (3 бита информации предекодирования на каждый байт инструкции). Предекодирование при загрузке в кэш позволяет вынести накладные расходы на определение границ инструкций за пределы каналов декодирования и поддерживать постоянный темп декодирования вне зависимости от длины и структуры команд.
Процессоры загружают команды из кэша блоками, из которых выделяются инструкции, направляемые на декодирование. Процессор архитектуры К10 производит выборку инструкций из кэша команд L1I выровненными 32-байтными блоками, в отличие от процессоров K8 и Intel Core 2, которые производят выборку 16-байтными блоками. Темп выборки, составляющий 16 байт за такт, позволяет K8 и Core 2 отправлять на декодирование в каждом такте по пять инструкций средней длиной до 5 байт. Однако длина x86-инструкций может достигать 16 байт, и в некоторых алгоритмах длина нескольких смежных команд в цепочке может превышать 5 байт, что делает невозможным декодирование по три команды за такт в таких случаях (рис. 1).
Рис. 1. Несколько смежных длинных команд ограничивают темп
декодирования при выборке 16-байтными блоками В частности, длина SSE2 – простой инструкции с операндами типа регистр-регистр (например,
movapd xmm0, xmm1) – составляет 4 байта. Однако при применении в команде адресных операций обращения к памяти с использованием базового регистра и смещения (например,
movapd xmm0, [eax+16]) длина команды увеличивается до 6–9 байт в зависимости от смещения. В 64-битном режиме при использовании дополнительных регистров к коду команды добавляется ещё один однобайтный REX-префикс. Таким образом, в 64-битном режиме длина SSE2-команд может достигать 7–10 байт. Длина SSE1-команды на 1 байт меньше, если это команда векторная (то есть над четырьмя 32-битными значениями), но скалярные (над одним операндом) SSE1-команды также могут достигать длины в 7–10 байт при тех же условиях.
Выборка 16 байт за такт не является ограничением в подобной ситуации для процессора K8, так как он всё равно не может декодировать векторные SSE-команды в темпе выше 3 команд за 2 такта, однако для архитектуры K10 16-байтная выборка могла бы стать ограничением, поэтому расширение ширины выборки до 32 байт за такт является обоснованным решением.
Кстати говоря, процессоры Core 2 выбирают инструкции блоками по 16 байт, как и процессоры K8, поэтому они могут эффективно декодировать поток команд в темпе по 4 за такт лишь в том случае, когда средняя длина инструкции не превышает 4 байт, в противном случае декодер не сможет эффективно обрабатывать не только 4, но и 3 инструкции за такт. Однако Core 2 обладают специальным внутренним 64-байтным буфером, хранящим последние запрошенные четыре 16-байтных блока. Выборка из этого буфера осуществляется с темпом 32 байта за такт. Этот буфер позволяет кэшировать короткие циклы, снимать в них ограничение на темп выборки, а также экономить 1 такт на каждом предсказании перехода на начало цикла. Однако циклы не должны содержать более 18 команд, более 4 условных переходов, и не должно быть ни одной команды ret.
Предсказание переходов
Когда в потоке команд встречаются ветвления, процессор, чтобы не прерывать декодирование, должен попытаться угадать дальнейшее поведение программы и продолжить декодирование с наиболее вероятной ветви. В таких случаях выборка очередного блока инструкций производится с использованием механизма предсказания переходов. Предсказание переходов в процессорах K8 производится по адаптивному двухуровневому алгоритму, который учитывает историю переходов не только текущей инструкции, но и 8 предыдущих инструкций. Основным недостатком механизма предсказания переходов в K8 было отсутствие предсказания косвенных переходов с динамически чередующимися адресами.
Косвенными называются переходы, которые производятся по указателю, динамически вычисляемому при выполнении кода программы. Обычно косвенные переходы вставляются компиляторами в конструкции switch-case, а также используются при вызовах функций по адресу и в вызовах виртуальных функций в объектно-ориентированном программировании. Процессор K8 всегда пытается произвести выборку кода по последнему адресу перехода. Если адрес изменился, происходит сброс конвейера. В случае, если адрес перехода периодически чередуется, процессор будет постоянно ошибаться. Механизм предсказания динамически изменяющихся адресов косвенных переходов впервые появился в процессоре Pentium M. Отсутствие такого механизма в K8 снижает его эффективность на объектно-ориентированном коде.
Как и ожидалось, предсказание условных переходов в K10 было усовершенствовано:
Появился механизм предсказания динамически изменяющихся адресов косвенных переходов, которые предсказываются по таблице размером 512 элементов.
С 8 до 12 бит увеличен размер глобального регистра истории, который используется для определения истории последовательности предыдущих команд переходов.
С 12 до 24 элементов увеличена глубина стека возврата, который используется для быстрого определения адреса возврата из функции, чтобы продолжить выборку, не дожидаясь, пока команда ret получит адрес возврата из стека.
Благодаря этим усовершенствованиям K10 должен получить ощутимую прибавку в скорости исполнения программ, написанных на объектно-ориентированных языках высокого уровня. К сожалению, объективно оценить эффективность блока предсказания переходов K10 довольно трудно, но по некоторым данным она может в некоторых случаях быть ниже, чем в процессорах Intel.
Декодирование
Полученные из кэша команд блоки копируются в буфер предекодирования (
Predecode/Pick Buffer), где происходит выделение инструкций из блоков, определение их типов и отсылка в соответствующие каналы декодера. Простые инструкции, которые декодируются в одну (Single) или две (Double) макрооперации, отсылаются в "простой" декодер, называемый
DirectPath. Сложные инструкции, которые декодируются в 3 или более макрооперации, отсылаются в микропрограммный декодер, называемый
VectorPath.
Рис. 2. Декодер Каждый такт из каналов декодеров могут выходить по 3 макрооперации (МОП). DirectPath-декодером за такт может быть декодировано 3 простых 1-МОПовых инструкции, либо одна 2-МОПовая и одна 1-МОПовая, либо полторы 2-МОПовых инструкции (три 2-МОПовых инструкции за два такта). Сложные инструкции могут декодироваться более чем в 3 МОПа, поэтому декодирование таких инструкций может продолжаться в течение нескольких тактов. Чтобы не создавать конфликтов на выходе из каналов декодера, простые и сложные инструкции в K8 и K10 не могут быть отправлены на декодирование одновременно.
МОПы состоят из пары микроопераций (micro-ops): одной микрооперации целочисленной или вещественной арифметики и одной адресной микрооперации обращения к памяти. Выделение микроопераций из МОПов производится планировщиком, который отправляет их на исполнение независимо друг от друга.
Выходящие каждый такт из декодера МОПы объединяются в группы по 3. Из-за чередования DirectPath и VectorPath команд или различных задержек в выборке инструкции для декодирования на выходе декодера может сформироваться группа, содержащая 2 или даже всего 1 МОП. Такая группа заполняется до трёх пустыми МОПами и в таком виде отправляется на исполнение.
Векторные SSE, SSE2 и SSE3-команды в процессоре K8 разбиваются на пары МОПов, раздельно обрабатывающие старшую и младшую 64-битные половины 128-разрядного SSE-регистра на 64-битных устройствах. Это в два раза снижает темп декодирования команд и в два раза уменьшает количество инструкций, попадающих в очередь планировщика.
Благодаря тому, что в процессоре К10 блоки FPU расширены до 128 бит, отпала необходимость дробления векторных SSE-команд на два МОПа. Большинство SSE-инструкций, которые декодировались в K8 как DirectPath Double, в K10 стали декодироваться в 1 МОП как DirectPath Single. Кроме того, часть SSE-инструкций, которые в K8 декодировались через микропрограммный VectorPath-декодер, в K10 стали декодироваться через простой DirectPath-декодер с уменьшением числа генерируемых МОПов до 1 или 2-х МОПов (в зависимости от операции).
Также упростилось декодирование целочисленных инструкций работы со стеком. Большинство форм инструкций работы со стеком, которые обычно используются при вызовах функций CALL-RET и PUSH-POP, теперь тоже декодируются простым декодером в один МОП. Кроме того, эти команды теперь при помощи специальной схемы
Sideband Stack Optimizer преобразуются в независимую цепочку макроопераций, которые могут исполняться параллельно.
Sideband Stack Optimizer
В K10 к схемам декодера был добавлен специальный блок, называемый Sideband Stack Optimizer. Принцип его действия аналогичен новому блоку Stack Pointer Tracker, применяемому в процессорах Core. Зачем он нужен? В системе команд x86 для вызова функции, выхода из неё, передачи в неё параметров и сохранения содержимого регистров используются команды CALL, RET, PUSH и POP. Все эти команды в неявной форме используют регистр ESP, указывающий на текущее положение стека. Проследить, как исполняются эти команды при вызове функции в K8, можно, представив их декодирование как последовательность эквивалентных элементарных операций изменения регистра стека и загрузки/сохранения:
Как видно из этого примера, при вызове функции команды последовательно меняют регистр ESP, поэтому каждая команда неявно зависит от результата предыдущей. Команды в этой цепочке не могут быть переупорядочены, поэтому тело функции, начиная с команды mov eax, [esp+16], не может начать выполняться до выполнения последней команды PUSH. Блок Sideband Stack Optimizer отслеживает изменение стека и преобразует цепочку в независимую, настраивая смещение каждой команды относительно стека и вставляя операции синхронизации вершины стека (sync-MOP) перед командами, явно использующими регистр стека. Тем самым снимается ограничение на переупорядочивание команд, использующих стек.
Команда
mov eax, [esp+16], с которой начинаются вычисления в теле функции в данном примере, зависит только от операции синхронизации вершины стека. Эти операции теперь свободно могут быть выполнены параллельно с другими командами перед ними. Таким образом, скорость передачи параметров и сохранения регистров увеличивается, и при этом тело функции может начать загружать параметры и производить операции с ними ещё до того, как будут завершены передача всех параметров и сохранение регистров.
Таким образом, повышение темпа декодирования команд работы со стеком, применение блока Sideband Stack Optimizer, увеличение глубины стека возврата и предсказание чередующихся косвенных переходов в K10 приводят к ощутимому увеличению скорости выполнения кодов, богатых вызовами функций.
Декодер процессора K10 не сможет декодировать по 4 команды за такт, как это может делать в благоприятных условиях декодер Core 2, но это не будет являться ограничивающим фактором при исполнении программ. Средний темп исполнения команд практически никогда не достигает 3 команд за такт, поэтому декодер K10 будет достаточно эффективен, чтобы вычислительные блоки не простаивали от нехватки операций в очередях.
Блок управления командами
Декодированные тройки МОПов поступают в блок управления командами (instruction control unit – ICU), который заносит МОПы в буфер переупорядочивания (reorder buffer – ROB). Буфер переупорядочивания состоит из 24 линий по три МОПа. Каждая тройка МОПов записывается в свою линию. Таким образом, ROB позволяет блоку управления отслеживать состояние 72 МОПов вплоть до их отставки.
Из буфера переупорядочивания МОПы отсылаются в очереди планировщиков целочисленных и вещественных исполнительных устройств в том порядке, в котором они вышли из декодера. Тройки МОПов продолжают храниться в буфере переупорядочивания до тех пор, пока не будут выполнены и отставлены все более старые операции. Во время отставки производится запись окончательных значений в архитектурные регистры и память. Отставка операций, удаление информации о них из ROB и запись окончательных значений производится в программном порядке, в котором операции поступили в буфер переупорядочивания. Это необходимо для того, чтобы в случае исключения или прерывания отменить результаты всех последующих операций, выполненных во внеочередном порядке.
Исполнение целочисленных команд
В процессорах K8 и K10 блок целочисленных операций (
Integer Execution Unit) состоит из трёх симметричных целочисленных каналов. Каждый из вычислительных каналов имеет свой планировщик с очередью на 8 МОПов, одинаковый набор целочисленных арифметико-логических устройств (ALU), адресных устройств (AGU) и блок условных переходов. Кроме этого, к вычислительному каналу 0 подключён блок умножения, а к вычислительному каналу 2 – блок выполнения новых операций LZCNT и POPCNT (о них ниже).
Рис. 3. Блок исполнения целочисленных операций Выбор очереди для каждого МОПа определяется статическим положением команды в тройке, сформированной на выходе из декодера. Каждая макрооперация из тройки отсылается из буфера переупорядочивания на исполнение в свою очередь, что, с одной стороны, упрощает управление командами, а с другой – может привести к несбалансированной загрузке очередей при неблагоприятном расположении цепочки зависимых операций в коде программы (что, однако, на практике почти не встречается и потому слабо влияет на производительность). Операции умножения и расширенные битовые операции размещаются декодером в нужных слотах троек, чтобы обеспечить их попадание в заданный канал.
В очередях планировщиков вычислительных каналов МОПы, как было сказано выше, разбиваются на целочисленные микрооперации и адресные микрооперации обращения к памяти. По мере готовности данных планировщик может запускать на исполнение из каждой очереди одну целочисленную операцию в устройство ALU и одну адресную операцию в устройство AGU. Количество одновременных обращений к памяти ограничено двумя. Таким образом, за каждый такт может запускаться на исполнение 3 целочисленных операции и 2 операции с памятью (64-битного чтения/записи в любой комбинации). Микрооперации из различных арифметических МОПов отправляются на исполнение из очередей по мере готовности данных для них во внеочередном порядке. После того как арифметическая и адресная микрооперации из МОПа выполнены, МОП удаляется из очереди планировщика, освобождая место для следующих операций.
В процессоре K8 адресные микрооперации обращения к памяти выбираются в программном порядке. Более поздние по порядку в программе операции обращения к памяти не могут запускаться перед более ранними. Это приводит к тому, что невозможность вычислить адрес для более ранней адресной операции блокирует все последующие адресные операции, даже если все операнды для последующих операций уже готовы.
Например:
add ebx, ecx
mov eax, [ebx+10h] – быстрое вычисление адреса
mov ecx, [eax+ebx] – адрес зависит от результата предыдущей команды
mov edx, [ebx+24h] – эта команда не будет запущена до тех пор, пока не будут вычислены адреса всех предыдущих команд. Это может приводить к потере производительности и является одним из ограничивающих факторов в процессоре K8, из-за которого на некоторых кодах K8, несмотря на возможность запуска двух команд чтения за такт, исполняет команды обращения к памяти менее эффективно, чем процессор Core 2, запускающий одну команду чтения за такт, но при этом обладающий механизмом спекулятивного внеочередного исполнения команд чтения в обход предшествующих команд чтения и записи.
В процессорах архитектуры K10 это узкое место ликвидировано. Процессоры K10 теперь способны не только запускать вне очереди команды чтения, но и запускать команды записи раньше команд чтения в тех случаях, когда процессору известно, что адреса записи и чтения не конфликтуют. Запуск записи в обход чтения позволяет ощутимо ускорить выполнение некоторых видов кодов, например, циклов, начинающихся с команды чтения очередной порции данных из памяти и заканчивающихся сохранением вычисленного результата в память.
L1:
mov eax, [esi] // загрузка данных
.....// операции над данными
mov [edi] , eax // сохранение результата
cmp
jnz L1
В таких случаях процессор, не поддерживающий запуск чтения раньше записи, не может начать выполнения следующей итерации цикла до завершения записи результата текущей. Процессоры, которые поддерживают переупорядочивание чтения, могут начать загрузку данных и вычисления для следующей итерации, не дожидаясь окончания текущей.
К сожалению, процессор K10 пока не умеет производить спекулятивную загрузку в обход записи по ещё неизвестному адресу, как это делают процессоры Core 2. Несмотря на то, что такие спекуляции могут приводить к штрафам, в реальности в коде программ эти штрафы редки (около 5 % случаев), поэтому спекулятивные загрузки оправданы с точки зрения повышения производительности.
Ещё одним усовершенствованием целочисленного блока процессора K10 явилась оптимизация алгоритма команды целочисленного деления. Теперь скорость выполнения команды целочисленного деления зависит от старших значащих битов делимого и делителя. Например, в тех случаях, если делимое равно нулю, деление выполняется практически в два раза быстрее. Вообще говоря, целочисленное деление – очень редкая операция, которой из-за невысокой скорости выполнения всячески стараются избегать в реальных программах, заменяя её умножением на число, обратное делителю, сдвигами или обходя другими способами, поэтому данная оптимизация, скорее всего, не принесёт заметного вклада в производительность приложений.
В целом блок целочисленных устройств К10 будет весьма эффективен. После добавления механизма внеочередного исполнения операций с памятью в нём теперь отсутствуют ярко выраженные слабые места. Несмотря на меньшую глубину очередей, по сравнению с процессорами Core 2, у процессоров K10 отсутствуют ограничения на чтение регистров из регистрового файла и некоторые другие ограничения планирования, которые не позволяют Core 2 постоянно исполнять операции в максимально возможном темпе.
Исполнение вещественных команд
В процессорах K8 и K10 планировщик блока операций с плавающей точкой (FPU) отделён от планировщика целочисленных команд и устроен несколько иначе. Буфер планировщика вмещает до 12 групп по 3 МОПа (36 вещественных операций теоретически). В отличие от блока исполнения целочисленных команд с симметричными вычислительными каналами блок плавающей арифметики содержит три различных устройства: FADD для вещественного сложения, FMUL для вещественного умножения и FMISC (он же FSTORE) для команд сохранения в памяти и вспомогательных операций преобразования, поэтому в буфере планировщика нет привязки положения МОПа в группе команд к конкретному вычислительному устройству (рис. 4).
Рис. 4. Блок исполнения операций с плавающей точкой Каждый такт K8 и K10 могут запускать на исполнение по одной операции в каждое из устройств вещественной арифметики. В процессоре K8 устройства вычислений с плавающей точкой являются 80-битными. Векторные 128-битные SSE-команды разбиваются на этапе декодирования на два МОПа, которые производят операции над 64-битными половинами 128-битного операнда и запускаются на исполнение последовательно в разных тактах. Это не только ограничивает темп выполнения векторных команд, но и практически в два раза уменьшает эффективный объём буфера FPU-планировщика и, следовательно, глубину внеочередного выполнения команд.
В процессоре K10 ширина FPU-устройств увеличилась до 128 бит. K10 обрабатывает векторные 128-битные операнды целиком одной операцией, что увеличивает теоретический темп выполнения векторных SSE-команд в два раза по сравнению с K8. Кроме того, за счёт сокращения вдвое количества МОПов увеличивается эффективная длина очереди планировщика, что позволяет делать более глубокое внеочередное исполнение.
В процессоре K8 SSE-команды загрузки выполняются с использованием устройства FSTORE, что, с одной стороны, не позволяет выполнять одновременно другие команды, претендующие на это устройство, а с другой – ограничивает количество одновременно запускаемых команд загрузки до одной. Два параллельных чтения из памяти в K8 может быть выполнено только в том случае, если одна из команд – это инструкция, совмещающая обращение к памяти и операцию с данными (так называемая Load-Execute-команда), например,
ADDPS xmm1, [esi].
В процессоре K10 произошло несколько важных усовершенствований механизма исполнения SSE-команд загрузки.
Во-первых, команды загрузки данных больше не используют ресурсы FPU-блока, таким образом, теперь освобождается порт FSTORE для запуска других команд, а команды загрузки могут запускаться по 2 за такт.
Во-вторых, в тех случаях, когда данные в памяти выровнены по 16-байтной границе, команды невыровненной загрузки данных MOVU** работают теперь так же эффективно, как и команды выровненной загрузки MOVA**. Таким образом, для процессоров K10 использование команд MOVA** больше не даёт никаких преимуществ.
В-третьих, в процессорах K10 применение невыровненных загрузок также теперь разрешено и для Load-Execute-команд, совмещающих загрузку с операцией над данными. Обычно, если нет уверенности в том, что данные в памяти выровнены, компилятор (или программист) использует команды MOVU** для чтения данных в регистры с последующими операциями над регистрами. Использование невыровненных загрузок непосредственно с командами Load-Execute может значительно сократить количество отдельных команд загрузки в коде программы и тем самым увеличить производительность. Поддержка этой возможности должна быть встроена в компиляторы. Вообще говоря, по спецификации SSE, разработанной компанией Intel, обращение Load-Execute-команды по адресу, не выровненному по границе 16 байт, должно приводить к исключению. Для сохранения совместимости со спецификацией разрешение невыровненных загрузок в Load-Execute-командах должно включаться установкой специального флага программным обеспечением, спроектированным и скомпилированным с учётом новых возможностей процессоров.
В-четвёртых, две шины чтения данных из кэша первого уровня в процессоре K10 расширены до 128 бит. Это позволяет процессору выполнять по два чтения 128-битной порции данных каждый такт. Это очень важная особенность архитектуры, так как для параллельного выполнения двух команд нужно 4 операнда (по 2 на команду), а в ряде алгоритмов поточной обработки данных два из четырёх операндов, как правило, считываются из оперативной памяти. Напротив, две шины записи в процессоре K10 по-прежнему остались 64-битными, а 128-битный результат при записи в память разбивается на два 64-битных пакета. Таким образом, процессор может выполнять каждый такт только одну 128-битную запись, или два 128-битных чтения, или одно 128-битное чтение и один 64-битный пакет записи. Однако с учётом того факта, что количество чтений обычно не менее чем в два раза превосходит количество записей, ограничение записи не должно заметно сказываться на эффективности процессора при обработке 128-битных данных.
В-пятых, команды копирования 128-битных данных MOV*** регистр-регистр теперь могут исполняться на любом из трёх FPU-устройств, а не только на FADD и FMUL, что также освобождает блоки FADD и FMUL для целевых операций.
Как мы видим, FPU-блок процессора K10 стал значительно более гибким. Появились такие уникальные возможности, пока отсутствующие у процессоров Intel, как эффективная невыровненная загрузка, в том числе для Load-Execute-команд, и два 128-битных чтения за такт. В отличие от Core 2, планировщики вещественных и целочисленных операций используют раздельные очереди, что помогает избегать конфликтов операций из-за одних и тех же портов запуска. Однако K10 по-прежнему разделяет устройство FMISC (FSTORE) для операций SSE-сохранения с некоторыми командами преобразования данных, что может в отдельных случаях сказаться на темпе их исполнения.
В целом FPU-блок в K10 обещает быть весьма эффективным, по ряду параметров (например, возможности двух 128-битных чтений за такт и эффективной невыровненной загрузке) превосходящим FPU-блок Core 2.
Подсистема памяти
Устройство загрузки/сохранения В процессоре K8 после вычисления на AGU адресов обращения к памяти операции загрузки и сохранения направляются в LSU (Load/Store Unit) – устройство загрузки/сохранения. В LSU находятся две очереди LS1 и LS2. Сначала операции загрузки и сохранения попадают в очередь LS1 глубиной 12 элементов. Из очереди LS1 в программном порядке по две операции за такт производятся обращения к кэш-памяти первого уровня. В случае кэш-промаха операции перемещаются во вторую очередь LS2 глубиной 32 элемента, откуда выполняются обращения к кэш-памяти второго уровня и оперативной памяти.
В процессоре K10 в LSU были внесены изменения. Теперь в очередь LS1 попадают только операции загрузки, а операции сохранения направляются в очередь LS2. Операции загрузки из LS1 теперь могут исполняться во внеочередном порядке с учётом адресов операций сохранения в очереди LS2. 128-битные операции сохранения, как было сказано выше, обрабатываются в процессоре K10 как две 64-битные, поэтому в очереди LS2 они занимают по две позиции.
Кэш первого уровня Кэш первого уровня в процессорах K8 и K10 раздельный, по 64 КБ для команд (L1I) и для данных (L1D). Ассоциативность кэшей равна двум, размер линии – 64 байта. Низкая ассоциативность может приводить к частым конфликтам строк, претендующих на один набор, что может увеличивать количество кэш-промахов и негативно сказываться на производительности. Низкая ассоциативность частично компенсируется достаточно большим объёмом кэша L1. Большим преимуществом кэша L1D является его двухпортовость – он может обслуживать по две команды чтения и/или записи за такт в любой комбинации.
В процессоре K10 размер и ассоциативность кэша первого уровня, к сожалению, остались неизменными. Единственным заметным усовершенствованием кэша первого уровня в K10 стало увеличение разрядности шины данных на чтение. Теперь процессор, как было сказано в предыдущей главе, может производить по два 128-битных чтения каждый такт, что значительно повышает его эффективность при обработке SSE-данных в локальной памяти.
Кэш второго уровня В двух- и четырёхъядерных процессорах архитектур K8 и K10 каждое из ядер имеет индивидуальный кэш второго уровня L2. Размер кэша второго уровня в K10 остался равным 512 КБ на каждом из ядер, ассоциативность равна 16. У раздельных кэшей второго уровня есть свои достоинства и свои недостатки по сравнению с общим кэшем второго уровня в процессорах Core 2. К достоинствам можно отнести отсутствие конфликтов и конкуренции за кэш при одновременной интенсивной нагрузке нескольких ядер. К недостаткам – меньший объём кэша, приходящийся на одно ядро при интенсивной работе одной задачи.
Кэш L2 имеет эксклюзивную организацию хранения данных: данные в кэше первого и второго уровня не дублируются. Кэши первого и второго уровней обмениваются данными по двум однонаправленным шинам – одной на получение данных, другой на отправку. В процессоре архитектуры K8 ширина каждой шины 64 бита (8 байт) (рис. 5а.). Из-за такой организации процессор получает данные, запрошенные в L2, с невысоким темпом в 8 байт за такт. То есть передача 64-байтной строки занимает 8 тактов, что существенно увеличивает задержку получения данных ядром, особенно при одновременном доступе к двум или более строкам кэша второго уровня.
По окончательно не подтверждённой пока информации, в процессоре K10 разрядность шин приёма и передачи увеличилась в 2 раза, то есть до 128 бит каждая (рис. 5б). Это значительно сократит латентность доступа к кэшу при одновременном запросе двух или более строк.
Кэш третьего уровня Для компенсации недостаточного объёма индивидуальных кэшей второго уровня в процессоре K10 появился общий для всех ядер кэш третьего уровня L3 объёмом 2 МБ, с ассоциативностью, равной 32. Кэш L3 имеет адаптивную эксклюзивную организацию: в нём хранятся как данные, вытесненные из кэшей L2 всех ядер, так и общие данные, используемые несколькими ядрами. При получении запроса от ядра на чтение строки производится проверка: если строка используется только одним ядром, то она удаляется из L3, освобождая место для строки, вытесняемой из кэша L2 запрашивающего ядра. Если же строка используется и другим ядром, то она останется в кэше; при этом, чтобы освободить место для строки, вытесняемой из кэша L2, из кэша L3 будет удалена другая, более старая строка.
Кэш L3 также должен помочь увеличить скорость обмена данными между ядрами. Как мы выяснили ранее, обмен данными между ядрами в современных процессорах Athlon 64 происходит через шину памяти. Это значительно снижает скорость доступа к совместно используемым модифицируемым данным. Согласно материалам AMD, в четырёхъядерных процессорах архитектуры K10 обмен данными между ядрами может происходить через кэш L3. При получении запроса от другого ядра, ядро, хранящее модифицированные данные, копирует их в L3, откуда они будут прочитаны запрашивающим ядром. Скорость доступа к изменённым данным в кэше чужого ядра должна существенно возрасти. Когда нам представится возможность, мы это обязательно проверим :).
Рис. 6. Передача данных между ядрами в процессоре K10 Латентность кэша L3, очевидно, будет выше, чем латентность кэша L2, однако в материалах AMD говорится, что она будет меняться адаптивно в зависимости от нагрузки – при отсутствии большой нагрузки будет лучше латентность, при высокой нагрузке будет увеличиваться темп. Что за этим стоит на самом деле, нам ещё предстоит проверить.
TLB Кроме кэш-памяти для команд и данных, в процессорах существует ещё одна разновидность кэш-памяти – буфера трансляции виртуальных адресов в физические (translation-lookaside buffer – TLB). Они используются для хранения соответствия между виртуальными и физическими адресами страниц, полученных по таблицам трансляции страниц. Количество буферов трансляции определяет, как много страниц памяти может быть одновременно использовано без дополнительных дорогостоящих преобразований по таблицам. Это особенно критично для приложений, которые обрабатывают данные памяти в случайном порядке, когда постоянно происходят обращения к данным в разных страницах. В процессоре K10 было существенно увеличено количество буферов трансляции. Для удобства восприятия они сведены в таблицу.
Таблица 1 – Объём TLB процессоров K8 и K10 Как видно из таблицы, существенно увеличено количество буферов, используемых для трансляции адресов 2-МБ страниц, а так же появилась поддержка больших 1-ГБ страниц, которые будут полезны для серверов, обрабатывающих большие объёмы данных. При поддержке со стороны операционной системы приложения, использующие большие 2-МБ и 1-ГБ страницы, смогут получить прирост производительности.
Контроллер памяти В тех случаях, когда запрашиваемые данные не были обнаружены в кэшах всех уровней, происходит обращение к контроллеру памяти, интегрированному на кристалл процессора. Интеграция контроллера на кристалле процессора значительно снижает задержки при обращении к памяти и вместе с тем привязывает процессор к конкретному типу памяти, а также увеличивает площадь ядра и добавляет проблем с отбраковкой кристаллов. Контроллер памяти был одной из сильных сторон процессоров K8, однако в некоторых случаях его эффективность была недостаточной. В процессоре K10 контроллер памяти был значительно усовершенствован.
Во-первых, он теперь может работать в режиме передачи данных не только по одному 128-битному каналу, но и по двум независимым 64-битным каналам, что делает одновременное обращение к памяти со стороны нескольких ядер более эффективным.
Во-вторых, в контроллере был оптимизирован алгоритм планирования и переупорядочивания операций. Контроллер памяти группирует операции чтения и записи таким образом, чтобы наиболее эффективно использовать шину памяти. Операции чтения имеют преимущество перед операциями записи. Данные, предназначенные для записи, откладываются в буфере, объём которого в настоящее время точно не известен, но, по различным данным, лежит в диапазоне от 16 до 30 64-байтных строк. Выгрузка группы из нескольких отложенных строк позволяет значительно сократить расходы на переключение шины памяти с режима чтения на запись и обратно. Это особенно повышает производительность в случае потока чередующихся запросов чтения и записи.
В-третьих, контроллер памяти умеет анализировать последовательности запросов и делать предвыборку.
Предвыборка Предвыборка не является сильным местом процессоров K8. Интегрированный контроллер памяти с низкой латентностью долгое время позволял процессорам AMD показывать хорошую производительность при работе с памятью. Однако при работе с новой памятью DDR2 процессоры K8 не показали высокую эффективность, в отличие от процессоров Core 2, обладающих мощной системой предвыборки. В процессорах K8 два блока предвыборки – один для кода, другой для данных. Блок предвыборки для данных производит предвыборку в кэш второго уровня по упрощённым последовательностям.
В K10 предвыборка была усовершенствована.
Во-первых, предвыборка в K10 осуществляется непосредственно в кэш первого уровня, что позволяет скрывать латентность кэша второго уровня при обращении к данным. Хотя это и увеличивает вероятность засорения кэша L1 ненужными данными, особенно учитывая низкую ассоциативность кэша, однако, по утверждению AMD, себя оправдывает и повышает производительность.
Во-вторых, был реализован механизм адаптивной предвыборки, который динамически изменяет дистанцию предвыборки таким образом, чтобы обеспечить своевременность прибытия данных и не засорять кэш данными, в которых пока нет необходимости. Гибкость блока предвыборки была увеличена: теперь он может обучаться на запросах к памяти по любым адресам, а не только по адресам, попадающим в смежные строки. Кроме этого, блок предвыборки теперь учитывает программные команды предвыборки.
В-третьих, отдельный блок предвыборки был добавлен непосредственно в контроллер памяти. Контроллер памяти анализирует последовательности запросов от ядер и подгружает данные в буфер записи, оптимально используя шину памяти. Хранение строк предвыборки в буфере записи позволяет не засорять кэш-память и при этом значительно сокращать латентность обращения к данным.
В итоге мы видим, что подсистема памяти в процессорах K10 претерпела изменения в лучшую сторону. Но всё же надо отметить, что по ряду характеристик она потенциально уступает подсистеме памяти в процессорах Intel. Это отсутствие спекулятивной загрузки в обход записи по ещё неизвестному адресу, более низкая ассоциативность кэша L1D, более узкая (по темпу передачи данных) шина между кэшами L1 и L2, меньший объём L2 и более простая предвыборка. Несмотря на усовершенствования, предвыборка в Core 2 потенциально более мощная, чем у K10: у последнего, например, отсутствует предвыборка по адресам инструкций, позволяющая отслеживать поведение отдельных инструкций, а также предвыборка из L2 в L1, позволяющая эффективно маскировать латентность L2. Эти факторы по-разному могут влиять на разные приложения, но в ряде случаев могут обеспечивать более высокую производительность процессоров Intel.
Рассмотрим вкратце, какие ещё нововведения несёт нам архитектура K10.
Новые команды Процессор K10 теперь поддерживает несколько новых команд, расширяющих его возможности.
1. Команды расширенных битовых операций над регистрами общего назначения:
LZCNT – Count Leading Zeros – подсчитывает количество старших нулевых битов в операнде;
POPCNT – Bit Population Count – подсчитывает количество единичных битов в операнде.
2. Команды обработки SSE-регистров, называемые SSE4a:
EXTRQ – извлекает заданное количество битов из заданной позиции в младшей 64-битной части SSE-регистра;
INSERTQ – вставляет заданное количество битов в заданную позицию в младшей 64-битной части SSE-регистра;
MOVNTSS, MOVNTSD – команды потокового (без использования кэш-памяти) сохранения скалярных вещественных значений.
Расширение системы команд, называемое SSE4a – самостоятельное, оно никак не пересекается с новыми расширениями Intel, называемыми SSE4.1 и SSE4.2.
Виртуализация AMD продолжила совершенствовать свою технологию виртуализации, используемую для запуска нескольких операционных систем на одном компьютере. Одним из самых значимых улучшений виртуализации стало использование вложенных таблиц страниц (Nested Paging). В этом режиме таблицы страниц виртуальных машин вложены в глобальную таблицу страниц гипервизора. При отсутствии ссылки на страницу в TLB операции табличного преобразования производятся процессором автоматически, в отличие от "теневого" управления страницами (Shadow Paging), которое требует большого количества ресурсов для управления табличными преобразованиями виртуальных машин.
По некоторым данным, благодаря использованию вложенных таблиц страниц скорость работы приложений в виртуальной машине возрастает до 40 % по сравнению со скоростью исполнения этих приложений при использовании "теневых" таблиц страниц.
Управление питанием и частотой В новых процессорах K10 внедряется новая схема управления питанием и частотой ядер. Каждое из ядер теперь может работать независимо от других на своей частоте, которая меняется динамически в зависимости от нагрузки каждого из ядер.
Рис. 8. Независимое управление частотой
ядер в процессорах K10 При этом то, каким образом будет согласовываться частота работы общего для всех ядер кэша L3, пока остаётся неизвестным. Напряжение на всех ядрах одинаковое и определяется самым нагруженным ядром. Контроллер памяти управляет своим напряжением независимо от ядер и может понижать его при отсутствии большой нагрузки.
Выводы
Вся информация по новым процессорам AMD пока ещё не опубликована, поэтому нас ещё могут ждать сюрпризы. Однако основные выводы по микроархитектуре сделать уже можно. Новый процессор AMD, благодаря многочисленным улучшениям ядра, обещает существенный прорыв в производительности по сравнению со своим предшественником, особенно в приложениях с интенсивными вещественными вычислениями. В большом спектре приложений процессор будет способен бороться на равных с одночастотными процессорами Intel и побеждать их. Дополнительный прирост производительности могут получить приложения, написанные с учётом новых уникальных возможностей процессора, таких как эффективная невыровненная загрузка и поддержка больших 1-ГБ страниц. Однако в процессоре есть и слабые, по сравнению с процессорами Intel, стороны – это подсистемы кэширования и предвыборки, которые могут оказать отрицательное влияние на производительность в ряде приложений. Но самым главным недостатком в борьбе за высочайшую производительность на стартовом этапе скорее всего станет недостаточно высокая частота. Пожелаем же компании AMD скорейшего освоения новых частот и продолжим следить, как обе компании продолжат борьбу и будут дальше совершенствовать свои процессоры в борьбе за нас, потребителей.
Автор выражает благодарность Марии Малич и Сергею Романову aka GReY за помощь в подготовке статьи.