1 ядро, 2 потока. SMT, Hyper-threading. Как это работает?

На прошлой неделе я рассказал вам о том, какими могут быть будущие Intel процессоры, а также в общих чертах описал как работают их конвейеры.

К моему удивлению никто не задался вопросом — а где же тут Hyper-threading? Как говориться — покажи, ткни.

Ведь описанные схемы на деле работают в качестве двух логических ядер, а очевидно, что тут ядер — одно.

Однако на самом деле ядра в текущее время для современных процессоров — это уже некоторая условность.

По сути производительность и количество реально параллельно решаемых задач процессором intel зависит от количества портов, о которых я рассказывал в прошлом материале.

В AMD как таковых портов нет, но есть разделение конвейера на два пути для целочисленных и операций с плавающей запятой, где так же есть по несколько исполнительных устройств и именно суммарное количество исполнительных устройств определяет количество параллельно решаемых процессором задач.

А вот как для операционной системе будет показываться одно ядеро — это вопрос того как процессор может коммуницировать со внешним миром.

Но, не всё так просто.

Если бы было всё так просто — intel и AMD уже давно бы так ядра бы и считали, а то у видеокарт ядер тысячи, а у процессоров еле за десятку переваливает.

Есть очень важные различия между процессорами и видеокартами.

Видеокарты решают задачи, которые можно смело и спокойно разделить на миллионы, а то и миллиарды однотипных подзадач? во многом в силу большого количества полигонов и пикселей. Поэтому нет огромной сложности в том, чтобы каждый такт заполнять все блоки исполнительных устройств и производители видеокарт поэтому и называют их ядрами. С центральными процессорами всё куда сложнее. Они постоянно меняют решаемые задачи, именно поэтому широкий набор исполнительных устройств не считается как большое количество ядер. И одни исполнительные устройства не могут заменить другие. То есть одни задачи передаются через одни порты, где есть нужное устройство, а другие задачи — через другие порты.

И того получается, что центральные процессоры, в отличие от процессоров видеокарт — занимают не все свои исполнительные устройства каждый такт. Безусловно — производители процессоров организуют обвязку этих исполнительных устройств так, чтобы каждый такт занимать настолько много исполнительных устройств, насколько это возможно. Для этого организуются очереди выполнения, чтобы выбирать наборы микроопераций, которые могут быть выполнены параллельно в одном такте.

Но в реальности всё равно не всегда получается занять все исполнительные устройства. Если говорить про AMD, так и вовсе задача у процессора может быть сравнительно длительное время только для целочисленных устройств, в таком случае вообще половина процессора простаивает.

И с развитием мощностей процессоров проблема эта становиться более острой, так как про оптимизацию кода уже толком никто не задумывается, вернее на сверхвысокоуровневых языках программирования её сделать нельзя. То что используют программисты — работает через какие-то библиотеки, о функционировании которых программисты не знают.

И вся надежда на то, что компилятор сможет сотворить чудо (чего, естественно, не происходит) и в простых задачах появляются ненужные циклы на миллионы операций, выбираются какие-то не те типы данных под переменные, так сказать, с запасом. И появляются суммарно миллиарды лишних, и что ещё печальнее, полностью однотипных операций для процессора.

Что это будет значить для центрального процессора?

Для него это будет значить, что он раз за разом, такт за тактом будет использовать только один из всех исполнительных устройств, а все другие будут практически постоянно стоять в простое. В таком случае — теоретическая и практическая производительность будут отличаться в разы, естественно не в пользу практической.

Чтобы было нагляднее — представим эту ситуацию на схеме. Тут по вертикали у меня расположены порты к исполнительным устройствам, а по горизонтали — такты работы процессора.

Плохая для процессора задача каждый такт занимает только один порт, и ещё периодически требуется задействование портов, используемых для данных. Можно увидеть, что почти весь процессор находиться в простое. Не задействован его потенциал и на 20%. И не надо путать это с процентом загрузки процессора. Показанная ситуация в программах мониторинга процессора будет показывать 100% загрузку процессора

Проблема эта вполне реальная и, естественно, производители процессоров с ней борются.

И самый очевидный метод — разрешить операционной системе выполнять вместе несколько программ. Вакантных для работы портов, очевидно настолько много, что особых проблем с тем чтобы выполнять ещё что-то нет.

Допустим мы разрешили операционной системе распознавать ядро не как одно, а как два. В таком случае — операционная система на одно ядро подмешивает два выполняемых процесса. Естественно оба эти процесса декодируются в микрооперации и помещаются в общую очередь переупорядочивания и выполнения микроопераций. И из этой очереди с большим шансом процессор сможет выбрать задачи так, чтобы заполнять ещё какие-то порты в каждом такте, которые бы в противном случае пустовали бы.

Естественно работает это не 100% идеально. То есть если задача более менее нормальная, то она, благодаря построению очередей, может занимать по несколько портов каждый такт.

Другая задача тоже будет занимать по несколько портов каждый такт.

Если просто тупо совместить эти два потока — то часто выходило бы и то, что задачи по портам пересекались бы.

Естественно так как задачи расположены будут в одной очереди, то процессор не допустит конфликтов, но в тоже время — из-за этого выполнение двух задач на одном ядре будет всё равно немного медленнее, чем выполнения задач каждой на своём ядре.

Но и надо понимать, что процессоры проектируют тоже не дураки, и подбирают наборы исполнительных устройств за каждым из портов не случайным образом.

Допустим, зачастую для обычных задач нужен ALU, а не какие-то более редкие устройства. Поэтому если обоим программам нужен будет в основном ALU, то у текущих процессоров intel ALU есть во всех 4-х портах с вычислительными исполнительными устройствами, и в таком случае конфликтов не будет и процессор просто будет разводить задачи на разные ALU и при таких задачах, скорее всего отличия от удвоения производительности будет связано с ограничением при работе с данными.

Из рассказа может показаться, что это запихивание двух потоков в одно ядро штука чисто программная.

Во многом оно, конечно, так и есть. Но и для аппаратной начинки — место тоже нашлось.

В общем-то если бы это была чисто софтверная фишка, то скорее всего производители процессоров и операционных систем уже смогли бы реализовать динамическую мультипоточность. То есть отслеживать степень занятости исполнительных устройств процессора, и добавлять виртуальные потоки на каждое ядро пока ядро не будет забиваться задачами до отказа.

На деле всё сложнее и есть и недостатки такого подхода.

Первая проблема — изолированное выполнение разных потоков.

Несмотря на общие очереди процессор не должен разрешать использовать данные одного потока для работы другого. И это, кстати, одно из мест в которых возникают аппаратные уязвимости процессоров.

То есть стороннее приложение может попытаться в обход защиты получить данные предназначенные для другого приложения. Естественно если попытаться реализовать динамическую систему мультипоточности, то скорее всего, останутся в кешах и регистрах какие-то подвешенные данные до их замещения новыми после закрытия потока для ядра. И этим смогут пользоваться приложения вирусы.

Ещё одна проблема — это общие данные для потоков. Не обязательно, что на нескольких потоках раскиданы два разных приложения. Одно приложение тоже может быть распараллелено. Если создавать и завершать потоки, то образуется сумятица по внутренним адресам данных, то есть надо будет тратить много времени на завершение процессов, запись результатов под новыми внешними для процессора адресами, потом остальные потоки нужно будет заново обеспечить перекрёстными данными. В общем — суматоха та ещё. Собственно эта суматоха и приводит к появлению уязвимостей.

Ещё одна проблема — обработка прерываний от изначально неизвестного количества потоков. Если количество потоков на ядро будет меняться, то процессор должен быть всё равно адаптирован под максимально возможное количество потоков, чтобы обеспечить возможность штатно отрабатывать прерывания.

То есть на каждый из потоков в ядре может одновременно прийти какое-то действие вызывающее прерывание и приоритетное выполнение. Неопределённое количество потоков создаёт проблемы, опять же, в том числе, и с изменением перекрёстных данных для разных потоков в одном ядре.

В общем мультипоточность — это, к сожалению, не чисто софтверная штука, а имеет и аппаратные ограничения и просто так менять на лету число потоков на ядро тоже нельзя.

Но даже учитывая строгое задание числа потоков на ядро есть недостатки и у такого решения.

В intel есть разделение регистров на потоки на входе в процессор инструкций. Физически этого деления нет, но для каждого потока выделяется строго половина из доступного объёма регистровой памяти. Это, скорее всего, нужно как раз для дальнейшей защиты данных одного потока от другого, то есть возможно первоначальные адреса в регистрах сами по себе указывают на то, для какой операции надо разрешать доступ к тем или иным данным.

Естественно и удельного объёма кеш памяти тоже на один поток становиться меньше. То есть для эффективной работы нужно больше кеша на ядро.

Ну, и, конечно, есть проблемы и софтверные.

Буквы «H T» в логотипе появились не спроста

Я, как человек купивший себе в 2003 году 4-ый пентиум с одним ядром и двумя потоками, могу вас заверить, что оптимизация под многопоток и под гипертрединг однозначно нужна. Без этой оптимизации процессор становиться медленнее для каждой задачи, даже если второй поток ядра простаивает.

И тогда, в 2003 году отключение HT было довольно действенной мерой по повышению производительности.

Сейчас отчасти это тоже работает в некоторых задачах на процессорах с очень большим числом ядер по тем же причинам, то есть не оптимизированный софт под нужное количество потоков и сложности организации работы связки — процессор, операционная система и выполняемая программа при работе с настоящими ядрами и с разделенными на потоки.

Остаётся ещё вопрос — числа потоков на ядро более чем два. Возможно ли это? И тут ответ однозначный — возможно. У IBM есть процессоры с 8 потоками на ядро прямо сейчас.

Вопрос актуальности 4-х потоков на ядро в обычных процессорах — стоит под вопросом. Я думаю, что разработчики процессоров имеют статистику по реальной занятости исполнительных устройств. И, учитывая, что 3-х или 4-х поточные ядра они не делают — означает, что, скорее всего, плотность занятости исполнительных устройств достаточно высокая; так что увеличение потоков в большей части задач будет ухудшать производительность из-за уменьшения удельного объема кеша на поток, или будет сильно снижать эффективность использования транзисторного бюджета, или усложнять внутренние передвижения инструкций и данных внутри конвейера.

Но не исключено, что кто-то из производителей пойдет на такой путь, допустим — увеличит число исполнительных устройств на ядро и вместо увеличения числа ядер чтобы занять все эти исполнительные устройства производители увеличат количество потоков на одно ядро. Вполне возможно, что этот путь может быть более рациональным, чем увеличения числа настоящих ядер. В таком случае кто-то из производителей к нему прибегнет.

Видео на YouTube канале "Этот компьютер"

Добавить комментарий