Как работает один поток на нескольких ядрах?

61

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

Основываясь на моем чтении Hyper-threading , кажется, что ОС организует инструкции всех потоков таким образом, что они не ожидают друг друга. Затем интерфейс ЦП дополнительно организует эти инструкции, распределяя один поток по каждому ядру, и распределяет независимые инструкции из каждого потока по всем открытым циклам.

Таким образом, если существует только один поток, то ОС не будет выполнять какую-либо оптимизацию. Тем не менее, внешний интерфейс ЦП будет распределять независимые наборы команд между каждым ядром.

Согласно https://stackoverflow.com/a/15936270 , конкретный язык программирования может создавать больше или меньше потоков, но это не имеет значения при определении того, что делать с этими потоками. ОС и процессор справляются с этим, так что это происходит независимо от используемого языка программирования.

введите описание изображения здесь

Просто для пояснения, я спрашиваю об одном потоке, запущенном на нескольких ядрах, а не о запуске нескольких потоков на одном ядре.

Что не так с моим резюме? Где и как разделены инструкции потока между несколькими ядрами? Имеет ли значение язык программирования? Я знаю, что это широкая тема; Я надеюсь на понимание на высоком уровне.

Evorlor
источник
6
Набор инструкций для одного программного потока может выполняться на многих ядрах, но не сразу.
Кролтан
1
Вы смешиваете программные потоки (которые включают планировщик ОС) и аппаратные потоки или HyperThreading (функция ЦП, которая заставляет одно ядро ​​вести себя как два).
Угорен
2
У меня 20 водителей и 4 грузовика. Как это возможно, что один водитель может доставить посылки двумя грузовиками? Как это возможно, что один грузовик может иметь несколько водителей? Ответ на оба вопроса одинаков. По очереди.
Эрик Липперт

Ответы:

84

Операционная система предлагает интервалы времени ЦП для потоков, которые могут работать.

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

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

Если имеется несколько ядер, N, то операционная система планирует запуск наиболее подходящих N потоков на ядрах.

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

Однако операционная система может свободно предлагать временные интервалы одного потока на разных процессорах, и она может вращаться через все процессоры на разных временных интервалах. Однако, как говорит @ gnasher729 , он не может запускать один поток одновременно на нескольких процессорах.

Гиперпоточность - это аппаратный метод, с помощью которого одно расширенное ядро ЦП может поддерживать выполнение двух или более разных потоков одновременно. (Такой ЦП может предлагать дополнительные потоки при более низкой стоимости в кремниевой области, чем дополнительные полные ядра.) Это расширенное ядро ​​ЦП должно поддерживать дополнительное состояние для других потоков, например значения регистров ЦП, а также имеет состояние и поведение координации, которые обеспечивает совместное использование функциональных блоков в этом процессоре без объединения потоков.

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


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

Эрик Эйдт
источник
3
«Таким образом, выполнение одного и того же потока на одном процессоре в течение нескольких временных интервалов является преимуществом эффективности». Разве это не должны быть непрерывные кусочки времени? В противном случае кэши будут стерты другими потоками, нет? +1 за хорошее объяснение.
jpmc26
2
@Luaan: HT часто хорош, но ситуация не так проста, как вы описали. Пропускная способность внешнего интерфейса (4 мопа за такт на Intel, 6 на Ryzen) в равной степени распределяется между потоками (если только один не остановлен). Если это узкое место, то, как я уже сказал, HT вообще не поможет. Skylake нередко приближается к этому в хорошо настроенном контуре, если есть сочетание нагрузок, ALU и накопителей ... Транзисторы дешевы (и не могут все переключаться сразу, или процессор будет таять), поэтому современные процессоры x86 имеют больше исполнительных портов, чем может
Питер Кордес
2
... на нескольких портах) ... Это может показаться пустой тратой, но часто цикл использует только один тип исполнительного блока ALU одновременно, поэтому наличие дубликатов всего означает, что независимо от того, какой код выполняется, существует несколько порты для его инструкций. Таким образом, причина, по которой вы указали для получения выгоды от HT, не столь распространена, так как большая часть кода имеет некоторые нагрузки и / или хранилища, занимающие полосу пропускания внешнего интерфейса, и того, что осталось, часто недостаточно для насыщения исполнительных блоков.
Питер Кордес
2
@Luaan: Кроме того, в процессорах Intel целочисленные и FP / векторные исполнительные блоки используют одни и те же порты выполнения . Например, блоки FP FMA / mul / add находятся на портах 0/1. Но целочисленный множитель также находится на порту 1, и простые целочисленные операции могут выполняться на любом из 4 портов выполнения (диаграмма в моем ответе). Второй поток, использующий пропускную способность проблемы, замедлит их обоих, даже если они не конкурируют за исполнительные блоки, но часто есть выигрыш в чистой пропускной способности, если они не слишком сильно конкурируют за кеш. Даже хорошо настроенный высокопроизводительный код, такой как x264 / x265 (видеокодеры), выигрывает у Skylake около 15% от HT.
Питер Кордес
3
@luaan В дополнение к тому, что сказал Питер, ваше утверждение о том, что «это было первоначальное обоснование ХТ» , неверно. Первоначальное обоснование HT заключалось в том, что микроархитектура NetBurst удлинила конвейер до такой степени (для повышения тактовой частоты), что неправильные прогнозы и другие пузыри конвейера абсолютно убили производительность. HT был одним из решений Intel для минимизации времени, в течение которого исполнительные блоки этого большого дорогого чипа простаивали из-за пузырьков в конвейере: код из других потоков мог быть вставлен и запущен в эти дыры.
Коди Грей
24

Нет такой вещи, как один поток, работающий на нескольких ядрах одновременно.

Однако это не означает, что инструкции из одного потока не могут выполняться параллельно. Существуют механизмы, называемые конвейерной обработкой команд и выполнением вне порядка, которые позволяют это. Каждое ядро ​​имеет много избыточных ресурсов, которые не используются простыми инструкциями, поэтому несколько таких инструкций можно запускать вместе (при условии, что следующее не зависит от предыдущего результата). Тем не менее, это все еще происходит внутри одного ядра.

Гиперпоточность является своего рода крайним вариантом этой идеи, когда одно ядро ​​не только выполняет инструкции из одного потока параллельно, но и смешивает инструкции из двух разных потоков для дальнейшей оптимизации использования ресурсов.

Связанные записи в Википедии: конвейеризация инструкций , выполнение не по порядку .

Frax
источник
3
Они не могут работать одновременно, но они могут работать параллельно? Разве это не одно и то же?
Evorlor
10
@ Evorlor Ключевым моментом здесь является разница между ядром и исполнительным модулем. Один поток может работать только на одном ядре, но процессор может использовать динамический анализ, чтобы определить, какие инструкции, выполняемые ядром, не зависят друг от друга, и выполнять их одновременно на разных исполнительных блоках. Одно ядро ​​может иметь несколько исполнительных блоков.
user1937198
3
@Evorlor: ЦП не по порядку может находить и использовать параллелизм на уровне команд в потоке команд одного потока. например, часто инструкции, которые обновляют счетчик цикла, не зависят от некоторых других операций, выполняемых циклом. Или в a[i] = b[i] + c[i]цикле, каждая итерация независима, поэтому загрузка, добавление и сохранение из разных итераций могут выполняться сразу. Это должно сохранить иллюзию того, что инструкции выполняются в программном порядке, но, например, хранилище, которое отсутствует в кеше, не задерживает поток (пока не закончится место в буфере хранилища).
Питер Кордес
3
@ user1937198: Фраза «динамический анализ» лучше подойдет JIT-компилятору. Процессоры, вышедшие из строя, на самом деле не анализируют; это больше похоже на жадный алгоритм, который запускает любые инструкции, которые были декодированы и выданы, и их входные данные готовы. (Окно неупорядоченного переупорядочения ограничено несколькими микроархитектурными ресурсами, например, Intel Sandybridge имеет размер буфера ReOrder 168 моп. См. Также измерение экспериментального размера ROB ). Все реализовано с помощью аппаратных конечных автоматов для обработки 4 мопов за такт.
Питер Кордес
3
@Luaan Да, это была интересная идея, но компиляторы AOT все еще недостаточно умны, чтобы полностью ее использовать. Кроме того, Линус Торвальдс (и другие) утверждал, что разоблачение того, что большая часть внутренних частей конвейера является большим ограничением для будущих проектов. Например, вы не можете увеличить ширину конвейера без изменения ISA. Или вы создаете ЦП, который отслеживает зависимости обычным способом, и, возможно, выдает две группы VLIW параллельно, но тогда вы потеряли преимущество EPIC в отношении сложности ЦП, но все еще имели недостатки (потеря пропускной способности, когда компилятор не мог заполнить слово).
Питер Кордес
22

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

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


ОС организует инструкции всех потоков таким образом, чтобы они не ожидали друг друга.

ОС НЕ просматривает потоки команд потоков. Это только планирует потоки к ядрам.

Фактически, каждое ядро ​​выполняет функцию планировщика ОС, когда ему нужно выяснить, что делать дальше. Планирование является распределенным алгоритмом. Чтобы лучше понять многоядерные машины, представьте, что каждое ядро ​​работает отдельно. Как и многопоточная программа, ядро ​​написано так, что его код на одном ядре может безопасно взаимодействовать с его кодом на других ядрах для обновления общих структур данных (например, списка потоков, готовых к запуску).

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

Затем интерфейс ЦП дополнительно организует эти инструкции, распределяя один поток по каждому ядру, и распределяет независимые инструкции из каждого потока по всем открытым циклам.

Ядро ЦП выполняет только один поток инструкций, если оно не остановлено (спит до следующего прерывания, например, прерывание по таймеру). Часто это поток, но это также может быть обработчик прерываний ядра или другой код ядра, если ядро ​​решило сделать что-то иное, чем просто возврат к предыдущему потоку после обработки и прерывания или системного вызова.

В HyperThreading или других конструкциях SMT физическое ядро ​​ЦП действует как несколько «логических» ядер. Единственное различие с точки зрения ОС между четырехъядерным процессором с гиперпоточностью (4c8t) и обычным 8-ядерным компьютером (8c8t) заключается в том, что ОС с поддержкой HT будет пытаться планировать потоки для разделения физических ядер, чтобы они не конкурировать друг с другом. Операционная система, которая не знала о гиперпоточности, просто увидела бы 8 ядер (если вы не отключите HT в BIOS, она обнаружит только 4).


Термин « внешний интерфейс» относится к части ядра ЦП, которая выбирает машинный код, декодирует инструкции и выдает их в неупорядоченную часть ядра . Каждое ядро ​​имеет свой собственный интерфейс и является частью ядра в целом. Инструкции, которые он извлекает, - это то, что процессор в настоящее время работает.

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

Если вы замените «ядро» на «исполнительный блок» в своей идее, вы почти правы. Да, ЦП распределяет независимые инструкции / мопы параллельно исполняющим блокам. (Но есть перепутывание терминологии, так как вы сказали «front-end», когда на самом деле это планировщик команд CPU, также называемый Reservation Station, который выбирает инструкции, готовые к выполнению).

Внеочередное выполнение может найти ILP только на очень локальном уровне, только до пары сотен инструкций, а не между двумя независимыми циклами (если они не короткие).


Например, асм эквивалент этого

int i=0,j=0;
do {
    i++;
    j++;
} while(42);

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

На x86 цикл будет выглядеть примерно так:

top_of_loop:
    inc eax
    inc edx
    jmp .loop

Haswell имеет 4 целочисленных исполнительных порта, и все они имеют сумматоры, поэтому он может поддерживать пропускную способность до 4 incинструкций за такт, если они все независимы. (С задержкой = 1, поэтому вам нужно всего 4 регистра, чтобы максимизировать пропускную способность, сохраняя 4 incкоманды в полете. Сравните это с вектором FP-MUL или FMA: задержка = 5 пропускной способности = 0,5 требует 10 векторных аккумуляторов для поддержания 10 FMA в полете чтобы максимизировать пропускную способность. И каждый вектор может быть 256b, держа 8 плавающих одинарной точности).

Взятая ветвь также является узким местом: цикл всегда занимает по крайней мере один полный такт на одну итерацию, потому что пропускная способность принятой ветви ограничена 1 на такт. Я мог бы поместить еще одну инструкцию в цикл без снижения производительности, если только он не читает и не записывает, eaxили edxв этом случае это удлиняет эту цепочку зависимостей. Помещение в цикл еще 2 инструкций (или одной сложной многопользовательской инструкции) создаст узкое место на внешнем интерфейсе, так как он может выдавать только 4 мопа за такт в ядро ​​из строя. (См. Этот раздел вопросов и ответов, где приведены подробности о том, что происходит с циклами, которые не кратны четырем мопам: буферы цикла и кеш мопов делают вещи интересными.)


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

Емкость буфера переупорядочения является одним из факторов, ограничивающих размер окна не по порядку. На Intel Haswell это 192 моп. (И вы даже можете измерить это экспериментально , наряду с емкостью переименования регистров (размером файла регистра).) Ядра с низким энергопотреблением, такие как ARM, имеют гораздо меньшие размеры ROB, если они вообще выполняют не по порядку.

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

Если вы хотите узнать больше о внутренних процессорах ЦП, есть некоторые ссылки в вики-теге Stackoverflow x86 , в том числе на руководство по микроархиву Agner Fog , а также на подробные рецензии Дэвида Кантера с диаграммами процессоров Intel и AMD. Из его записи о микроархитектуре Intel Haswell это окончательная схема всего конвейера ядра Haswell (а не всего чипа).

Это блок-схема одного ядра процессора . Четырехъядерный процессор имеет 4 из них на чипе, каждый со своими кэшами L1 / L2 (совместно использующими кэш L3, контроллеры памяти и PCIe-соединения с системными устройствами).

Haswell полный трубопровод

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

Питер Кордес
источник
2
«Поиск и использование параллелизма (на уровне команд) в однопоточной программе выполняется исключительно аппаратно». Обратите внимание, что это относится только к обычным ISA, а не к VLIW, в которых ILP полностью определяется компилятором или программистом, или совместно между аппаратными средствами. и программное обеспечение.
Хади Брайс,
1
@ user7813604: да. Гиперпоточность не может распараллелить один поток. Он делает наоборот: он запускает несколько потоков на одном ядре, снижая производительность для каждого потока, но увеличивая общую пропускную способность.
Питер Кордес
1
@ user7813604: Весь смысл ILP в том, чтобы выяснить, какие инструкции можно выполнять параллельно, при этом сохраняя иллюзию, что каждая команда выполняется по порядку, каждая из которых заканчивается до начала следующей. Скалярный конвейерный ЦП может нуждаться в остановке иногда для зависимостей, если задержка больше 1. Но для суперскалярных ЦП это еще больше.
Питер Кордес
1
@ user7813604: да, мой ответ буквально использует это в качестве примера. Например, Haswell может выполнять до 4 incинструкций в одном такте с его 4 целочисленными исполнительными блоками ALU.
Питер Кордес
1
@ user7813604: Да, ILP - это то, сколько можно выполнить параллельно. Реальный процессор будет иметь ограниченную способность находить и эксплуатировать ILP, фактически выполняя его параллельно в одном ядре, например, до 4 суперскалярных в Intel. Этот ответ пытается объяснить это на примерах.
Питер Кордес