Одновременная многопоточность (Simultaneous multithreading, SMT) — это функция, позволяющая процессору одновременно обрабатывать команды из двух разных потоков. Но задавались ли вы когда-нибудь вопросом, как это работает? Как процессор отслеживает два потока и распределяет ресурсы между ними?
В статье я объясню, как устроена эта функция. Понимание внутреннего устройства SMT поможет вам решить, подходит ли она для ваших продакшен-серверов. Иногда SMT способна резко повысить производительность системы, но в некоторых случаях она приводит к замедлению. Знание подробностей позволит вам сделать правильный выбор.
Примечание: основная часть изложенного в статье относится к реализации SMT компании Intel, также называемой гипертредингом (hyper-threading). Она основана на научной статье компании, опубликованной в 2002 году.
Предыстория и причины создания гипертрединга
SMT была предложена для оптимизации использования ресурсов процессора. На уровне микроархитектуры процессоры состоят из сотен регистров, множества блоков загрузки/сохранения и арифметических блоков. Чтобы оптимальнее их использовать, процессоры применяют различные техники параллельности на уровне команд (instruction level parallelism, ILP), например, конвейерную обработку команд, суперскалярную архитектуру, исполнение с изменением очерёдности.
Процессор с конвейерной обработкой оптимизирует использование ресурсов, разбивая исполнение команды на несколько этапов, образующих конвейер. В каждом такте команда перемещается с одного этапа конвейера на следующий, а процессор добавляет на первый этап конвейера новую команду. На рисунке ниже показано, как работает конвейерная обработка для конвейера глубиной 5. Как видите, после пятого такта процессор обрабатывает в каждом такте до пяти команд, после чего каждый такт завершает одну команду.
Описание пятиэтапного конвейера команд. В каждом такте команда перемещается на новый этап, освобождая место на первом этапе для новой команды. В очень глубоком конвейере параллельно может обрабатываться множество команд.
Кроме того, современные процессоры являются суперскалярными, то есть вместо отправки в каждом такте одной команды они могут отправлять несколько. Например, процессоры Intel Core i7 могут отправлять в каждом такте по четыре команды. Этот параметр называется шириной конвейера (issue width) процессора.
Конвейерная обработка команд и суперскалярная архитектура существенно повышают пропускную способность обработки команд и оптимизируют использование ресурсов процессора. Однако на практике такого максимального использования бывает сложно достичь. Чтобы параллельно исполнять такое количество команд, процессору необходимо найти в программе достаточное количество независимых друг от друга команд, что сделать очень трудно.
Обычно это приводит к двум типам пустой траты ресурсов. Первый — это горизонтальные траты, возникающие, когда процессор не может найти в потоке достаточного количества независимых команд, чтобы заполнить ширину своего конвейера.
Второй тип — это вертикальные траты, возникающие, когда процессор не может отправить в такте ни одной команды, потому что следующие команды в программе зависят от исполняемых в данный момент.
Горизонтальные и вертикальные траты в процессоре c шириной конвейера в пять команд на такт. Пустые прямоугольники обозначают слоты команд, в которые процессор не может отправить команду
Один из способов оптимизации использования вычислительной мощи заключается в использовании традиционной многопоточности (multithreading), при которой процесс переключает контекст между несколькими потоками. В такой схеме за каждый такт процессор отправляет команды только для одного потока, поэтому при этом всё равно могут возникать пустые горизонтальные траты. Однако в следующем такте процессор может переключить контекст и отправить команды ещё одному потоку, избежав вертикальных трат. Это приводит к более полному использованию CPU, однако при большой ширине конвейеров горизонтальные траты тоже могут быть существенными. Кроме того, существуют дополнительные траты на переключение контекста между потоками.
Поэтому возникла идея одновременной многопоточности. Она позволяет процессору отправлять команды нескольким потокам в одном такте без трат на переключение контекста. По определению, команды разных потоков независимы и могут исполняться параллельно, что в конечном итоге приводит к полному использованию ресурсов исполнения.
Хотя идея SMT не накладывает ограничений на количество потоков, реализация SMT, созданная компанией Intel (hyper-threading), ограничивает его двумя потоками на ядро.
Реализация одновременной многопоточности на уровне микроархитектуры в процессорах Intel
Мы разобрались в причине появления SMT, а теперь давайте узнаем, как она реализована. Мы опишем подробности реализации, а также поговорим о том, как она работает.
Обычный процессор без SMT может за раз исполнять команды только одного потока, потому что с каждым потоком связан контекст, описывающий текущее состояние программы в процессоре, также называемое состоянием архитектуры. В него включены данные в регистрах, значение счётчика программы, регистров управления и так далее.
Для одновременного исполнения команд двух потоков процессору необходимо одновременно представлять состояние двух потоков. Поэтому для реализации SMT проектировщики оборудования дублировали состояние архитектуры процессора. Из-за этого единый физический процессор отображается в операционной системе как два логических процессора, чтобы она могла распределять для них потоки исполнения.
Двухъядерный процессор без SMT (сверху) и двухъядерный процессор с SMT (внизу). Для реализации SMT в нижнем процессоре состояние архитектуры было дублировано и он выглядит для ОС как четыре ядра.
Кроме того, на уровне микроархитектуры у процессора есть различные буферы и ресурсы исполнения. Для одновременного исполнения команд двух потоков эти ресурсы или дублируются, или делаются общими для двух логических процессоров. Выбор варианта (дублирование или совместное использование ресурса) зависит от множества факторов. Например, от стоимости дублирования ресурса с точки зрения энергопотребления и занимаемого на чипе пространства.
Самые важные подробности работы SMT находятся в её микроархитектурной реализации, так что давайте углубимся в неё.
▍ Микроархитектура процессоров
Архитектура набора команд (instruction set architecture, ISA) — это публичный интерфейс процессора, позволяющий программистам программировать CPU. ISA включает в себя набор команд и регистры, которыми могут пользоваться команды. Микроархитектура процессора — это его внутренние подробности реализации. Разные модели процессоров могут поддерживать одну ISA, но различаться на уровне микроархитектуры.
Микроархитектура состоит из трёх частей: фронтенда, бэкенда и retirement unit. На показанной ниже диаграмме представлена схема микроархитектуры современного процессора:
Схема микроархитектуры современного процессора состоит из фронтенда, бэкенда и retirement unit
Фронтенд — это часть, содержащая блок управления командами (получает и декодирует команды программы, которые будут исполняться следующими).
Бэкенд состоит из ресурсов исполнения, например, из физических регистров, арифметических блоков и блоков загрузки/хранения. Он подхватывает переданные фронтендом декодированные команды, выделяет под них ресурсы исполнения и распределяет их для исполнения. В retirement unit результаты выполненных команд помещаются в состояние архитектуры процессора.
Исполнение команд в процессоре с SMT
Чтобы понять, как работает SMT, мы подробнее поговорим о каждом из трёх компонентов микроархитектуры CPU. Давайте начнём с фронтенда.
▍ Реализация SMT во фронтенде
На рисунке ниже показан фронтенд микроархитектуры в увеличенном виде. Он состоит из множества компонентов, каждый из которых играет собственную роль в получении и декодировании команд. Давайте разберём их один за другим.
Фронтенд процессора X86. Источник: Intel Technology Journal, Vol 06, Issue 01, 2002.
▍ Указатели команд
Чтобы понимать, какие команды нужно получать, фронтенд имеет указатель команд, содержащий адрес следующей команды программы.
В случае процессора с SMT существует два набора указателей команд, независимо друг от друга отслеживающие следующую команду для двух программ.
▍ Трассовый кэш
Указатели команд содержат адреса следующий команд потоков, и фронтенд должен считывать эти команды из указанных адресов. Прежде чем это сделать, он сначала проверяет наличие этих команд в трассовом кэше (trace cache).
Трассовый кэш содержит недавно декодированные трассы команд. Декодирование команд — это затратная операция, а некоторые команды нужно исполнять часто. Наличие этого кэша помогает процессору снизить задержки, связанные с исполнением команд.
Трассовый кэш по необходимости динамически делается общим для двух логических процессоров. Если один поток исполняет больше команд, чем другой, то ему позволяется занять больше элементов трассового кэша.
Каждый элемент кэша имеет метку с информацией о кэше, чтобы различать команды двух потоков. Порядок доступа двух логических процессоров к трассовому кэшу выбирается в каждом такте.
▍ Кэш буфера ассоциативной трансляции команд (Instruction Translation Lookaside Buffer, ITLB)
Если фронтенд не нашёл команду в трассовом кэше (произошёл промах кэша), то он ищет команду по указанному адресу в кэше команд L1. Если произошёл промах и в кэше команд L1, то он вынужден получать команду из кэша следующего уровня или из основной памяти.
Данные кэшей команд L1 используют его виртуальный адрес, но для поиска в основной памяти требуются физические адреса. Чтобы транслировать виртуальные адреса в физические, используется буфер ассоциативной трансляции (ITLB), содержащий недавно транслированные виртуальные адреса.
В процессоре с SMT каждый логический процессор имеет собственный кэш ITLB.
Логика получения команд из основной памяти работает в порядке поступления, но она резервирует как минимум один слот запроса для каждого логического процессора, чтобы они оба могли двигаться дальше.
После прибытия команд из основной памяти они хранятся в маленьком потоковом буфере до того, как будут подхвачены для декодирования. Эти буферы тоже являются маленькими структурами, в процессорах с SMT они дублируются для логических процессоров.
▍ Очередь микрокоманд
После получения команд они декодируются в более мелкие и простые команды, называемые микрокомандами (uop). Эти uop помещаются в очередь микрокоманд (uop queue), используемую как граница между фронтендом и бэкендом CPU.
Очередь микрокоманд равно разделена между двумя логическими процессорами. Такое статическое разделение позволяет обоим логическим процессорам продвигаться вперёд независимо.
▍ Реализация SMT в бэкенде микроархитектуры
Как только завершается подготовка микрокоманд в uop queue, к своей работе приступает бэкенд. На изображении ниже показан в увеличенном виде бэкенд процессора Intel X86.
Бэкенд процессора Intel X86. Источник: Intel Technology Journal, Vol 06, Issue 01, 2002.
Давайте разберём то, что происходит в компонентах бэкенда.
▍ Распределитель ресурсов для исполнения с изменением очерёдности
Бэкенд берёт микрокоманды из uop queue и исполняет их. Однако он исполняет их не в исходном порядке программы.
Соседние команды программы обычно зависят друг от друга, и эти команды могут простаивать, потому что выполняют операцию с большой задержкой, например, чтение из основной памяти. В результате этого все зависимые от них команды вынуждены ждать. В такой ситуации ресурсы процессора тратятся впустую. Для решения этой проблемы используется движок исполнения с изменением очерёдности, который берёт более поздние команды программы и исполняет их вне исходного порядка.
Движок исполнения с изменением очерёдности состоит из распределителя, определяющего ресурсы, требуемые этим микрокомандам, и распределяющего их в зависимости от их доступности.
Распределитель в одном такте распределяет ресурсы для микрокоманд одного логического процессора, а в следующем такте переключается на другой логический процессор. Если uop queue содержит микрокоманды только для одного из логических процессоров или у одного из логических процессоров исчерпалась его доля ресурсов, то распределитель использует все такты для другого логического процессора.
▍ Общие ресурсы исполнения
Что же это за ресурсы, которые распределитель выделяет для микрокоманд, и как они становятся общими?
Первый ресурс, который требуется микрокомандам — это регистры. На уровне ISA процессор может иметь очень малое количество регистров (например, у X86-64 есть только 16 целочисленных регистров общего назначения), но на уровне микроархитектуры существуют сотни физических целочисленных регистров и схожее количество регистров с плавающей запятой. В процессоре с SMT эти регистры поровну поделены между двумя логическими процессорами.
Наряду с регистрами бэкенд также содержит множество буферов загрузки и хранения. Эти буферы используются для выполнения операций чтения и записи в память. В процессорах с SMT они тоже поровну разделены между логическими процессорами.
▍ Переименование регистров
Чтобы обеспечить возможность исполнения с изменением очерёдности, бэкенду также нужно выполнять переименование регистров. Так как на уровне ISA существует очень ограниченное количество архитектурных регистров, команды программы могут использовать один и тот же регистр для множества независимых команд. А движок исполнения с изменением очерёдности стремится использовать эти команды вне их исходного порядка и параллельно. Для этого он переименовывает исходные логические регистры, используемые в командах программы, в физические регистры. Такое отображение хранится в таблице псевдонимов регистров (register alias table, RAT).
Так как два логических процессора имеют собственные наборы архитектурных регистров, у них есть и собственная копия RAT.
▍ Очереди готовности команд
После этапов переименования регистров и распределителя команды почти готовы к исполнению. Они помещаются в два набора очередей — один для команд чтения/записи в память, второй — для всех остальных общих команд. Эти очереди тоже равно разделены между двумя логическими процессорами в ядре с SMT.
▍ Распределители команд
У процессора есть множество параллельно работающих распределителей команд. В каждом такте CPU часть команд из очередей готовности команд передаются распределителям. Очереди в каждом такте переключаются между командами двух логических процессоров, то есть в одном такте они отправляют команду одного логического процессора, а в другом переключаются на второй логический процессор.
У каждого распределителя есть небольшой внутренний буфер для временного хранения отправленных команд, пока распределитель не сможет запланировать их исполнение. Каждой из таких операций требуются определённые операнды и свободные блоки исполнения. Как только становятся доступными требуемые одной из команд данные и ресурсы, распределитель отправляет эту команду на исполнение.
Распределителям не важны логические процессоры, они исполняют микрокоманду сразу, как только становятся доступными необходимые ей ресурсы. Но для обеспечения справедливости для каждого логического процессора существует ограничение на количество активных элементов в очереди распределителя.
▍ Буфер изменения порядка
Когда исполнение функции завершается и её результат уже готов, он помещается в буфер изменения порядка. Команды исполняются в изменённом порядке, однако передавать их архитектуре процессора нужно в исходном порядке программы. Для этого используется буфер изменения порядка (reorder buffer).
Этот буфер в ядре с SMT поровну разделён между двумя логическими процессорами.
▍ Retirement Unit
Retirement unit (блок вывода) отслеживает, когда команды готовы для передачи состоянию архитектуры процессора, и выводит их в правильном порядке программы.
В ядре процессора с SMT retirement unit попеременно выводит микрокоманды каждого из логических процессоров. Если у одного из логических процессоров нет микрокоманд для вывода, то retirement unit тратит весь канал на другой логический процессор.
После вывода команды ему может также потребоваться выполнить запись в кэш L1. На этом этапе для выполнения таких записей задействуется логика выбора, которая также попеременно применяется в каждом такте к двум логическим процессорам для записи данных в кэш.
Подсистема памяти
Мы рассказали, как ресурсы исполнения ядра процессора делятся между логическими процессорами в системе с SMT, но пока не рассматривали доступ к памяти.
▍ Буфер ассоциативной трансляции
Буфер ассоциативной трансляции (translation lookaside buffer, TLB) — это маленький кэш, содержащий трансляцию виртуальных адресов в физические для доступа к данным. TLB по необходимости динамически делится между двумя логическими процессорами. Чтобы различать элементы для двух логических процессоров, каждый элемент также помечается идентификатором логического процессора.
▍ Кэши L1, L2 и L3
У каждого ядра CPU имеется собственный кэш L1. В зависимости от микроархитектуры кэш L2 тоже может быть личным или общим для ядер. Если присутствует кэш L3, то он бывает общим для ядер. Кроме того, кэши не знают о существовании логических процессоров.
Кэши L1 и L2 в двухъядерном процессоре. У каждого ядра есть свои личные кэши L1 и L2.
Так как кэш L1 (и иногда L2) является собственным для ядра, он по необходимости содержит данные для обоих логических процессоров. Это может вызывать конфликты и вытеснение данных, а также вредить производительности. С другой стороны, если потоки, запущенные на двух логических процессорах, работают с одним и тем же множеством данных, то общий кэш может повысить скорость их выполнения.
Влияние SMT на производительность
Мы уже рассмотрели практически все аспекты реализации SMT на уровне микроархитектуры и параллельного исполнения команд двух логических процессоров. Теперь давайте поговорим о влиянии на производительность.
▍ Выполнение одного потока на ядре с SMT
Как мы видели, использование SMT в ядре CPU требует общего использования между двумя логическими процессорами многих буферов и ресурсов исполнения. Даже если на ядре с SMT запущен только один поток, эти ресурсы остаются недоступными для этого потока, что снижает потенциальную производительность.
Наряду с пустой тратой общих ресурсов в отсутствие второго потока, существует и другое влияние на производительность. Операционная система выполняет на неиспользуемом логическом процессоре цикл простоя, ожидающий поступления команд. Этот цикл тоже впустую тратит ресурсы, которые можно было бы потратить на полное раскрытие потенциала другого логического процессора.
Похоже, если на ядре исполняется только один поток, в процессорах Intel Core не применяется разделение ресурсов. Об этом говорится как об улучшении, добавленном в этом поколении процессоров. Источник: Intel Technology Journal, Vol 14, Issue 3, 2010
Примечание из научной статьи Intel, в которой представлена новая микроархитектура процессоров Intel Core. Говорится, что в этом поколении процессоров отсутствуют лишние затраты, когда на ядре с SMT запущен только один поток.
▍ Выполнение двух потоков на ядре с SMT
Если на двух логических процессорах запущено два потока, то стоит подумать об их паттернах доступа к кэшу. Если потоки агрессивно используют кэш и конкурируют за него, то у них будут возникать конфликты и удаление данных друг друга, что снизит их производительность.
С другой стороны, если потоки по своей природе кооперативны, то они могут помогать повышению производительности друг друга. Например, если один поток создаёт данные, потребляемые другим потоком, то скорость их исполнения повысится благодаря хранению данных в общем кэше.
Если два потока не конкурируют за кэш, то они могут работать нормально, не вредя скорости исполнения друг друга, в то же время оптимизируя использование ресурсов ядра CPU.
Однако многие специалисты считают, что когда программе требуется максимальная производительность, лучше отключать SMT, чтобы один поток имел все доступные ему ресурсы.
▍ Уязвимости, связанные с SMT
Существуют и проблемы безопасности, связанные с SMT, они были обнаружены за несколько последних лет (см. например, здесь и здесь). Из-за общего использования ресурсов и спекулятивного исполнения команд многие из этих проблем создают возможности утечек конфиденциальных данных, чем может пользоваться нападающий. Поэтому в общем случае рекомендуется отключать SMT в системах. Кроме того, ходят слухи, что из-за этих проблем Intel может убрать hyperthreading из своего следующего поколения процессоров (Arrow Lake).
В заключение
Подведём итог. Понимание устройства и работы одновременной многопоточности (SMT) крайне полезно, когда нужно решить, следует ли использовать её в продакшен-серверах. SMT придумана для оптимизации использования ресурсов CPU и повышения скорости обработки команд. Она хорошо справляется с этим, позволяя одновременно выполнять несколько потоков, но у неё существуют и недостатки, о которых следует помнить.
При реализации SMT внутри процессора дублируются отдельные части, другие делаются общими или разделяются между потоками. Выигрыш от этого зависит от того типа работы, который выполняет CPU. Да, SMT способна оптимизировать использование ресурсов и общую производительность системы, но она может и создать конкуренцию за общие ресурсы и замедление отдельных потоков.
Ещё одним важным аспектом является безопасность. Недавно обнаруженные уязвимости показали, что совместное использование ресурсов в CPU с SMT может быть опасным. Оно может привести к раскрытию конфиденциальных данных, поэтому некоторые специалисты рекомендуют отключать SMT в системах, для которых очень важна безопасность.
Так пользоваться ли SMT? Это зависит от многих факторов. Если вашим рабочим нагрузкам требуется максимальная производительность и минимальные задержки, то этого можно достичь отключением SMT. Но если вы работаете с задачами общего назначения, способными выиграть от параллелизма, то стоит попробовать SMT.
Изучив эти подробности, вы сможете с большим основанием решать, что лучше подходит для вашей системы, и обеспечить наиболее эффективную (и безопасную) работу серверов.
Ссылки
Автор: ru_vds