Форум программистов, компьютерный форум, киберфорум
Наши страницы
Evg
Войти
Регистрация
Восстановить пароль
Рейтинг: 5.00. Голосов: 3.

Базовые принципы работы Hyper-threading

Запись от Evg размещена 30.12.2017 в 18:53
Обновил(-а) Evg 19.05.2018 в 18:30

Из-за технических ограничений форумного движка статью про hyper-threading пришлось превратить в цикл из четырёх статей. Данная статья является второй статьёй цикла. Четвёртая статья пока отсутствует





  • 1. Предисловие
  • 2. Hyper-threading в первом приближении
  • 3. Бесполезные ожидания при доступе в память
  • 4. Hyper-threading по существу
  • 5. Цикл холостого хода
  • 6. Должна ли ОС понимать, что процессор с hyper-threading'ом?
  • 7. При каких условиях есть польза от hyper-threading'а
  • 8. Что понимается под загрузкой процессора в диспетчере задач
  • 9. Заключение







1. Предисловие

Я уже объяснял принцип работы в статье Что такое Hyper-threading "на пальцах". Старая статья рассчитана на практически нулевой уровень подготовки читателя и объясняет лишь общую идею на пальцах, совершенно не вникая в технические подробности. Новая статья даёт более точное понимание, т.к. описывает некоторые технические детали, но при этом предполагает у читателя некий минимальный уровень знаний работы компьютера, а потому незнающему читателю что-то понять будет сложно, но попробовать можно. Очень желательно понимать, что такое кэш и зачем он нужен. Без этого не получится полноценно понять фундаментальную пользу hyper-threading'а

Из-за технических ограничений форума подготовительную часть материала пришлось выложить в отдельную статью Базовые принципы запуска пользовательских задач в операционных системах (т.е. первую статью цикла из трёх статей). Мне придётся к ней периодически обращаться, в этом случае я буду называть её "первая статья". В данной (второй) статье я буду пользоваться той же самой терминологией, что описана в первой статье в разделе 2 и тем же самым принципом отображения на диаграммах, что использовался в первой статье в разделе 3. Так же, как и в первой статье, я буду сильно упрощать или искажать конкретную техническую реализацию, оставляя правильной основную суть. Прочитать первой статью весьма желательно, т.к. без понимания принципов работы многозадачных операционных систем будет сложновато понять, ради чего создавался hyper-threading'а

В данной статье я буду описывать ту технологию, которая реализована в процессорах intel начиная с процессоров Core i3/i7 первого поколения (Nehalem). На более ранних версиях intel'овских процессоров (Pentium 4) уже была похожая технология под тем же названием hyper-threading, но устроена она была по другому, более подробно об этом я расскажу в третьей статье. То же самое касается и процессоров AMD Ryzen - технология устроена по другому, а потому данная статья к процессорам AMD Ryzen также не относится. Однако выводы для других технологий получаются похожими на выводы по части intel'овского hyper-threading'а. В интернете много статей про устройство hyper-threading'а, но основная масса их описывает ту технологию, которая была реализована в процессорах Pentium 4 и в процессорах AMD Ryzen - эти статьи содержат картинки с разноцветными клетками

Устройство hyper-threading'а относится к той части технической информации о процессоре, которая подробно НЕ описывается в документации, предназначенной для конечных пользователей. На сайте у intel'а про hyper-threading по большому счёту есть только маркетинговая чушь про то, что технология позволяет программам работать быстрее, не уточняя при этом, насколько быстрее. В основном мне приходилось черпать информацию от знающих (надеюсь) людей. Вполне может получиться так, что я имею некорректное или не полностью корректное представление о технической реализации, но в этом ничего страшного нет - общая суть вопроса и вытекающие из этого выводы от этого не меняются. Для понимания сути происходящего достаточно понимать базовые основы построения подобных систем, а выводы, как я уже говорил, будут примерно одинаковыми для любой конкретной реализации подобных технологий

2. Hyper-threading в первом приближении

Для начала рассмотрим, как выглядит hyper-threading в первом приближении. Для простоты рассмотрим одноядерный процессор с hyper-threading'ом (на маркетинговом языке будет обозначаться как "1 ядро/2 потока"), на котором запущены две счётные задачи. Такое ядро имеет то, что условно можно назвать словами "два входа" (на техническом языке это называется "2 логических ядра"). Операционная система в первом приближении видит его как двухъядерный процессор, т.е. может поставить одновременно две задачи на исполнение на разные входы. В реальности такое ядро имеет только одну материальную часть (т.е. одно физическое ядро) - один конвейер и один набор всех исполнительных устройств, которые должны присутствовать на одноядерном процессоре. Т.е. процессор "1 ядро/2 потока" является почти обычным одноядерным процессором, у которого просто есть два "входа". В процессе работы такого двухвходового ядра происходит постоянное переключение конвейера и исполнительных устройств между исполнением программы с первого входа и исполнением программы со второго входа. Т.е. задачи с двух входов в реальности исполняются в псевдопараллельном режиме. На диаграмме это будет выглядеть следующим образом:


Рисунок 1

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

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

3. Бесполезные ожидания при доступе в память

Если посмотреть на исполнение процессором счётной задачи, но в более мелком масштабе времени, то можно увидеть, что в процессе работы периодически возникают небольшие простои. Например, в случае чтения данных из памяти процессор обычно читает их из кэша. Но данные в кэше есть не всегда - в силу того, что кэш имеет размер, намного меньший, чем размер оперативной памяти. Если данных в кэше не оказалось (такое событие называется "cache miss"), то данные нужно будет считать из оперативной памяти (т.е. из физической планки памяти). Доступ к планке памяти выполняется довольно долго по процессорным меркам, время обращения исчисляется десятками (или даже сотнями) машинных тактов. Пока данные из памяти не получены, процессор не может выполнять над ними никакие операции, а потому ему не остаётся ничего, как щёлкать вхолостую в ожидании поступления данных из памяти. Процесс холостого щёлканья технически реализован в виде исполнения операции-пустышки. Такие пустышки в англоязычной литературе называют словом "bubble". Русскоязычные инженеры называют их словом "булька", а сам процесс холостого щёлканья - "бульканье". Т.е. процессор булькает, пока ему недоступны данные для вычислений. На диаграмме это будет выглядеть примерно так:


Рисунок 2

Прежде всего хочется отметить, что здесь используется более мелкий масштаб по времени (это выражено в виде изменившегося расстояния между отображением моментов прерываний от таймера). Серым цветом отмечены моменты времени, когда процессор булькает, ожидая поступления данных из памяти. Важным моментом является то, что ОС не знает о том, что процессор в это время ничего полезного не делает. С точки зрения ОС это время тратится на исполнение пользовательской задачи. Бульканье есть и в те моменты, когда мы находимся в чёрной области, но опять-таки подобные тонкости в рамках нашей статьи нам не интересны

Таким образом получается, что процесс исполнения пользовательской задачи является последовательностью исполнения процессором полезной нагрузки (коды задачи) и бесполезного бульканья, вызванного cache miss'ами и обусловленного медленной работой памяти. Эта ситуация с виду очень сильно напоминает потери времени, обусловленные медленной работой внешних устройств (см. первую статью рисунок 3). Проблему с медленно работающим диском ОС решает тем, что во время ожидания диска ставит на исполнение другую задачу (см. первую статью рисунок 6). Почему же проблемы с медленной работой памяти нельзя решить теми же способом, что и проблемы с медленно работающим диском? С виду ведь всё ровно то же самое, только в другом масштабе. Но по факту проблема кроется именно в масштабе. Медленно работающий диск создаёт временнЫе интервалы в миллионы и миллиарды машинных тактов. При этом процесс переключения между задачами, который выглядит как последовательное переключение процессора "задача1 -> ОС -> задача2", занимает тысячи тактов. Этой тысячей тактов можно пожертвовать ради того, чтобы заполнить полезной нагрузкой образовавшуюся пустоту длинной в миллион тактов. Но жертвовать тысячей тактов ради того, чтобы заполнить пустоту в десятки или сотни тактов, уже нецелесообразно. Другими словами, время переключения между задачами намного больше, чем потеря времени на ожидание ответа из памяти, а потому попытка эффективно потратить время привела бы к обратному эффекту. Получилось бы что-то типа стрельбы из пушки по воробьям

Часто ли возникают подобные простои, обусловленные медленной работой памяти? Или, что то же самое, часто ли возникают cache miss'ы? Всё очень сильно зависит от конкретной задачи. В процессоре есть несколько уровней кэша, есть контроллер памяти, совместными усилиями они пытаются минимизировать трафик между процессором и памятью. Но полностью устранить они его не могут. Если задача проводит большие вычисления, оперируя небольшим количеством данных (которые умещаются в объём кэша), то при работе такой задачи процессор практически не будет булькать, т.к. всё сгладит кэш. Но когда идёт обработка большого количества данных, то все они в кэше не уместятся и появится реальный траффик между процессором и памятью, из-за которого процессор начнёт булькать. В зависимости от задачи подобные простои могут составлять от 0% до условных 15% времени. Для простоты усредним эту величину и будем считать, что в среднем из-за cache miss'ов теряется порядка 10% от теоретически возможной производительности процессора. При этом если простои есть, то они случаются сравнительно часто. Т.е. образуется большое количество простоев, каждый из которых очень короткий по времени, но суммарно они дают весьма ощутимую величину в 10% (в среднем по больнице). ОС не в состоянии побороть большое количество маленьких простоев. Как же сними бороться?

4. Hyper-threading по существу

И вот тут на помощь нам приходит hyper-threading. Как мы уже видели в разделе 2, hyper-threading в какой-то мере выполняет функцию операционной системы по переключению задач, причём делает это практически мгновенно, не тратя на это тысячи тактов, как это делается при программном переключении через ОС. Как только процессор понял, что нужных данных нету в кэше, то он сразу же переключит конвейер на задачи с другого входа. На диаграмме это будет выглядеть так:


Рисунок 3

Как я уже описывал выше, ОС не знает о возникновении cache miss'а в процессоре. Но на диаграмме я отобразил бульканье процессора в той полоске, которая соответствует тому, как ОС видит ядра. Реально в процессоре в это время нету вообще никакого простоя, поскольку процессор переключился на другой вход. Нарисовав на диаграмме простой, я просто подсветил место, где он должен был бы возникнуть без hyper-threading'а, но не смог придумать, как это сделать более наглядно. Пока задачи работают без cache miss'ов, они просто выполняются по очереди, а наличие hyper-threading'а не даёт никаких преимуществ. Но как только случился cache miss и запрос в реальную память, то ядро, вместо того, чтобы потратить 100 тактов времени на бесполезное бульканье, переключает конвейер и исполнительные устройства на другой вход, получив таким образом преимущество в 100 тактов за счёт того, что появляется возможность потратить это время с пользой. Если в процессе исполнения задачи со второго входа случится cache miss, то ядро переключится на первых вход. И так далее по кругу. Ядро будет постоянно переключаться между двумя входами, исполняя задачу то с первого входа, то со второго. Переключение осуществляется либо при наступлении cache miss'а, либо после того, как текущий вход проработал некоторое количество тактов. В ядре есть счётчики, которые отслеживают, сколько тактов исполнялись коды с каждого из входов. Ядро пытается более-менее уравновесить значения этих счётчиков, обеспечивая этим равномерное исполнение задач с обоих входов. Таким образом, ядро с hyper-threading'ом выполняет некоторые минимальные функции ОС, сокращая при этом количество бульканья (чего ОС сделать не может). Т.е. hyper-threading является технологией, которая помогает ОС в переключении задач в те моменты времени, когда возникают простои

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

5. Цикл холостого хода

Что же делает ядро, когда у него имеется задача только на одном входе? Из первой статьи мы знаем, что ОС всегда что-то делает на всех доступных ядрах - каждое ядро загружено либо исполнением пользовательской задачи, либо исполнением кодов ОС, либо исполнением цикла холостого хода. При такой логике получается, что если на один вход ядра (на одно логическое ядро) поставить пользовательскую задачу, а на другой вход (на другое логическое ядро) - цикл холостого хода, то hyper-threading будет постоянно переключать физическое ядро между исполнением пользовательской задачи с одного логического ядра и исполнением цикла холостого хода со второго логического ядра, таким образом замедляя в два раза исполнение задачи. Т.е. на первый взгляд всё выглядит бредово

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

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

6. Должна ли ОС понимать, что процессор с hyper-threading'ом?

Глядя на диаграмму работы ядра с hyper-threading'ом появляется ощущение, что аппаратура реализована таким образом, что ОС вообще не должна знать, что из себя представляют те два логических ядра, которые она видит. Ведь можно с ними работать ровно таким же образом, как с настоящим двухъядерным процессором, совершенно не задумываясь о том, что на самом деле процессор одноядерный. Да, это так, но только при условии, что на процессоре только одно физическое ядро. В итоге два логических ядра, которые видит ОС, являются симметричными. Но если в процессоре есть два физических ядра с hyper-threading'ом, то видимые четыре логических ядра перестают быть симметричными. Если бы ОС не знала о том, что у нас процессор специфический, то могла бы поставить две пользовательские задачи на два входа, принадлежащие одному ядру и два цикла ожидания на два входа, принадлежащие другому ядру. В итоге одно ядро занималось бы исполнением двух задач в псевдопараллельном режиме, а второе ядро болталось бы в цикле холостого хода (точнее, находилось бы в режиме сна). Поэтому ОС должна понимать, что она видит не просто четыре ядра, ОС должна понимать, что это четыре логических ядра, распределённые на два физических ядра. Если у нас имеется две задачи, то нужно их поставить на логические ядра, соответствующие разным физическим ядрам. А если у нас имеется три задачи, то ОС должна постоянно жонглировать ими между четырьмя входами, чтобы обеспечить правильное равномерное исполнение трёх задач на двух физических ядрах


Рисунок 4

7. При каких условиях есть польза от hyper-threading'а

Допустим, у нас имеется одно обычное ядро без hyper-threading'а, на котором работают две счётные задачи. Допустим, интервал времени между таймером 10 миллисекунд, а ОС на каждом прерывании от таймера переключает исполнение между двумя задачами (т.е. вырожденный случай для простоты понимания). Это означает, что между двумя прерываниями в течение 10 миллисекунд (условные 30 миллионов тактов) ядро будет исполнять только одну задачу. В течение всех 10 миллисекунд ядро будет работать с данными из памяти, принадлежащими только одной задаче. Следовательно весь кэш будет содержать данные только этой задачи. Через 10 миллисекунд ОС поставит на исполнение вторую задачу, которая должна работать со своими данными из памяти. Но, предположим, что к этому времени у нас весь кэш уже забит данными первой задачи. Поэтому в течение какого-то периода времени при исполнении второй задачи мы будем иметь частые cache-miss'ы, соответственно в начальный период времени вторая задача будет исполняться медленно, и тормозить её будет процесс перетекания данных задачи из памяти в кэш (так называемый процесс "прогрева кэша"). Предположим, за условную 1 миллисекунду кэш прогреется, после чего оставшиеся 9 миллисекунд задача будет работать с высокой скоростью без cache miss'ов, поскольку все нужные данные уже осели в кэше. И так далее по кругу. Как только произойдёт переключение между задачами, то условная 1 миллисекунда будет тратиться на прогрев кэша, из-за чего задача будет исполняться медленно, а процессор будет часто булькать, а дальнейшие 9 миллисекунд задача будет исполняться быстро и без бульканья

Теперь включим в эту схему hyper-threading. Он будет переключать исполнение задач намного чаще, чем это делает ОС. Допустим, первая задача проработала 1 миллисекунду и успела за это время прогреть кэш. Затем hyper-threading переключил процессор на исполнение второй задачи. Поскольку весь кэш уже забит данными первой задачи, вторая задача начинает прогревать кэш. В процессе прогрева процессор, очевидно, начнёт булькать (т.е. у hyper-threading'а возникает повод для переключения входа), но для простоты понимания мы проигнорируем этот факт. В итоге вторая задача за 1 миллисекунду медленной работы прогреет кэш. Но тут hyper-threading обратно переключит процессор на исполнение первой задачи. Теперь уже первая задача будет 1 миллисекунду прогревать кэш. И так далее по кругу: hyper-threading будет переключать задачи с интервалом в 1 миллисекунду, а задачи будут только и делать, что выбивать друг у друга данные из кэша. В итоге получается, что без hyper-threading'а наши задачи работали в режиме "1 миллисекунда медленно, 9 миллисекунд быстро, и так далее по кругу", а с hyper-threading'ом работают в режиме "1 миллисекунда медленно и так далее по кругу". В память будут одновременно долбиться две задачи (т.е. темп запросов в память увеличится вдвое), что приведёт к удвоению нагрузки на кэш, а так же замедлению исполнения задач

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

Подведём промежуточные итоги написанному в этом и предыдущих разделах. Чтобы hyper-threading начал приносить пользу, нужно выполнение сразу нескольких условий. Во-первых, количество одновременно исполняемых потоков должно быть больше, чем количество физических ядер. Ведь ОС будет задействовать вторые входы на ядрах только в том случае, когда на всех ядрах уже заняты первые входы. А до того момента hyper-threading вообще не будет работать, потому что на каждом ядре активен будет только один вход или ни одного. Во-вторых, чтобы работающий hyper-threading начал давать преимущество, нужно чтобы более-менее часто случались cache-miss'ы, в противном случае мы получим ситуацию, описанную на рисунке 1, когда hyper-threading будет просто переключать входы ядра, нигде не получая преимущества. В-третьих, чтобы работающий hyper-threading не начал приносить вред, нужно, чтобы исполняемые на двух входах задачи НЕ работали с большим объёмом данных и частым обращением в память, в противном случае начнётся драка двух задач за кэш, как было описано в начале этого раздела. В последнем случае удвоение кэша спасло бы от того, что hyper-threading начинает вредить, но в реальности мы имеем, что количество дополнительного кэша составляет лишь треть от теоретически необходимого значения. Удвоение размера кэша обходится слишком дорого по деньгам, к тому же вносит лишние сложности, связанные с необходимостью развести на кристалле намного большее количество транзисторов. Т.е. финансово не выгодно

8. Что понимается под загрузкой процессора в диспетчере задач

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

Допустим, у нас есть одно ядро с hyper-threading'ом. Посмотрим, как будет выглядеть загрузка процессора в подобных программах в различных случаях

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

Теперь рассмотрим вариант, когда на каждом логическом ядре задача работает с реальной загрузкой в 50%. Т.е. программа, например, 10 миллисекунд работает, потом 10 миллисекунд спит или находится в состоянии ожидания. Когда такое поведение программы на двух логических ядрах, то физическое ядро всё равно будет загружено на 100%. Пока на первом входе задача спит, ядро исполняет задачу со второго входа, когда на втором входе задача ушла в сон, ядро исполняет задачу с первого входа. Таким образом, в мониторинговой программе отображается загрузка в 50% на двух логических ядрах, хотя мы имеем 100% загрузку физического ядра. Усреднённая загрузка по всем логическим ядрам будет отображаться как 50%

Может быть и такой вариант, когда исполняется только одна счётная задача. Она будет поставлена на первый вход, который будет загружен на 100% во времени. На втором входе будет висеть цикл холостого хода, соответственно его загрузка будет 0%. При этом физическое ядро будет загружено на 100%. В программе будет отображена 100% загрузка на одном ядре и 0% загрузка на другом ядре. таким образом средняя загрузка будет отображаться как 50%, создавая иллюзию того, что процессор загружен только на половину, хотя в реальности он загружен полностью

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

9. Заключение

Как мы увидели, hyper-threading не делает ничего умного. Он всего лишь помогает компенсировать простои ядра во времени, образованные, например, медленно работающей памятью. Эффектом hyper-threading'а служит лишь ускорение на условные 10% (в среднем по больнице), да и то только в определённых условиях: на обоих входах ядра должны быть поставленные на исполнение задачи, задачи должны иметь не слишком редкие, но и не слишком частые cache-miss'ы. Т.е. для того, чтобы от hyper-threading'а была хоть какая-то польза, нужно, чтобы на машине одновременно исполнялось больше задач, чем имеется в наличии физических ядер. В противном случае на каждое ядро будет поставлено на исполнение не более одной задачи, что попросту приведёт к тому, что процессор не станет включать hyper-threading. Не будем забывать, что в отдельных специфических случаях hyper-threading может приводить к замедлению, и это важно учитывать в случаях, когда процессор покупается условно под одну-единственную задачу

Изначальная идея hyper-threading'а состояла в том, что за счёт увеличения стоимости выпуска процессора на условные 5%, повысить производительность на условные 10% (в среднем по больнице). Но если мы посмотрим на стоимость процессоров с hyper-threading'ом и без него, то увидим, что разброс цен намного выше, чем 5%. И тому есть много разных причин. Одну из них мы уже обсудили - на процессор с hyper-threading'ом приходится устанавливать больше (не бесплатного) кэша. Если посмотреть более точно характеристики процессоров, то можно заметить, что процессоры без hyper-threading'а имеют более низкую частоту, чем ближайшая аналогичная модель с hyper-threading'ом. Это тоже объясняется довольно просто - на заводе все процессоры выпускаются с hyper-threading'ом, но технология изготовления чипов такова, что имеется сравнительно высокий выход брака. Бракованные процессоры могут, например, правильно работать только на более низкой частоте, или в них не работает hyper-threading, или в них не полностью работает кэш, или вообще работают не все ядра. Из таких бракованных процессоров делают более дешёвые модели - с меньшей частотой, с меньшим количеством ядер и кэша, без hyperthreading'а. Т.е. изначальная идея в какой-то мере играет ту же самую роль, что и была ей отведена - стоимость выпуска ВСЕЙ серии процессоров повышается на обозначенные 5%. А далее по различным соображениям стоимость выпуска всей серии НЕравномерно распределяется по разным моделям (в соответствии с разной степенью брака) - медленные модели подешевле, быстрые подороже. Есть подозрение, что присутствует в том числе и искусственное ценообразование - дешёвые модели продаются по более низкой цене, чем "справедливая" цена, а дорогие модели - по более высокой цене, чем "справедливая". Как итог получается, что модель процессора с hyper-threading'ом отличается от модели без hyper-threading'а не только наличием hyper-threading'а, но и более высокой частотой и объёмом кэша, т.е. разница между ними будет заведомо больше обозначенных 5%

Тем не менее, замерить чистый эффект от hyper-threading'а вполне реально - нужно просто взять процессор с hyper-threading'ом, и hyper-threading отключить (такая возможность обычно присутствует в bios'е материнской платы). В итоге мы получим процессор с той же частотой, с тем же количеством кэша, но с отключенным hyper-threading'ом

Свой вклад в ценообразование вносит в том числе и человеческая глупость и жадность. Hyper-threading сам по себе даёт сравнительно невысокий прирост к производительности. Но громкое слово с приставкой "hyper" для многих людей даёт ожидание того, что процессор будет работать чуть ли не с космической скоростью за весьма умеренные деньги. Хотя в реальности hyper-threading выполняет роль чуть ли не уборщика мусора, который по сусекам наскребает растерянные условные 10% производительности (в среднем по больнице), да и то лишь при определённых условиях. Маркетинговая терминология "количество потоков" подталкивает незнающих людей к выводу, что процессор "4 ядра/8 потоков" в два раза быстрее, чем "4 ядра/4 потока", а потому покупка "8-поточного" процессора, который на 50% дороже, чем "4-поточный" процессор - это очень выгодное приобретение с точки зрения соотношения цены и производительности. А в реальности в процессоре есть только ядра и нету никаких потоков (их придумали маркетологи), и реально 8-поточные задачи на процессоре "4 ядра/8 потоков" будут работать почти так же медленно, как и на процессоре "4 ядра/4 потока". Разница в скорости в первую очередь будет обеспечена более высокими частотами и бОльшим количеством кэша. Прирост непосредственно от "лишних 4 потоков" (если он вообще будет) скорее всего окажется сравнительно небольшим

Напоследок приведу конкретный пример: скорость майнинга криптовалюты XMR на процессоре Intel i7 6400T. Это инженерная версия процессора, можно считать, что это аналог core i7-6700, т.е. на процессоре имеется 4 ядра с hyper-threading'ом ("4 ядра/8 потоков" на маркетинговом языке) и 8 Мб кэша. Здесь приведена скорость вычислений в единицах "количество рассчитанных хэшей в секунду" в зависимости от количества используемых программных потоков. Замеры проводил не я, для полноты картины тут не хватает частоты работы процессора (она меняется из-за turbo-boost'а), но в целом нехватка этой информации не критична

1 поток: 48-49
2 потока: 95-97
3 потока: 131-133
4 потока: 135-138
5 потоков: 132-135
6 потоков: 129-131
7 потоков: 128-131
8 потоков: 128-131

При переходе от одного потока к двум скорость вычислений выросла примерно в два раза, т.е. в соотношении 2:1. Однако при переходе от двух потоков к трём скорость выросла уже не в соотношении 3:2, а меньше. Возможно, тут сказался turbo-boost, возможно, уже упёрлись в количество кэша, возможно, в оба фактора сразу. При переходе от трёх потоков к четырём уже наглядно видно, что прироста в скорости почти нет, и до соотношения 4:3 тут очень далеко. Это уже явно не проблема с turbo-boost'ом, а реальная нехватка кэша. Т.е. 8 мегабайт кэша ещё как-то хватало для эффективной работы трёх потоков, но при четырёх одновременно исполняемых потоках началась драка за кэш - исполняющиеся программные потоки на разных ядрах начинают выбивать друг у друга данные из кэша, т.к. каждый программный поток интенсивно использует более 2 мегабайт кэша. Дальнейшее увеличение программных потоков только ухудшает дело, т.к. негативный эффект от нехватки кэша намного превышает позитивный эффект от непосредственной работы hyper-threading'а

Нажмите на изображение для увеличения
Название: npic1.gif
Просмотров: 501
Размер:	10.9 Кб
ID:	4573Нажмите на изображение для увеличения
Название: npic2.gif
Просмотров: 605
Размер:	6.3 Кб
ID:	4574Нажмите на изображение для увеличения
Название: npic3.gif
Просмотров: 427
Размер:	12.6 Кб
ID:	4583Нажмите на изображение для увеличения
Название: npic4.gif
Просмотров: 503
Размер:	16.5 Кб
ID:	4584
Просмотров 591 Комментарии 1
Всего комментариев 1
Комментарии
  1. Старый комментарий
    Аватар для Evg
    Родил третью статью
    Запись от Evg размещена 19.05.2018 в 18:34 Evg вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru