итоги: поиск и использование параллелизма (на уровне команд) в однопоточной программе выполняется исключительно аппаратно, ядром процессора, на котором он работает. И только через окно из пары сотен инструкций, а не масштабного переупорядочения.
Однопоточные программы не получают никаких преимуществ от многоядерных процессоров, за исключением того, что другие вещи могут работать на других ядрах вместо того, чтобы отнимать время от однопоточной задачи.
ОС организует инструкции всех потоков таким образом, чтобы они не ожидали друг друга.
ОС НЕ просматривает потоки команд потоков. Это только планирует потоки к ядрам.
Фактически, каждое ядро выполняет функцию планировщика ОС, когда ему нужно выяснить, что делать дальше. Планирование является распределенным алгоритмом. Чтобы лучше понять многоядерные машины, представьте, что каждое ядро работает отдельно. Как и многопоточная программа, ядро написано так, что его код на одном ядре может безопасно взаимодействовать с его кодом на других ядрах для обновления общих структур данных (например, списка потоков, готовых к запуску).
В любом случае, ОС помогает многопоточным процессам использовать параллелизм на уровне потоков, который должен быть явно показан при написании многопоточной программы вручную . (Или компилятором с автоматическим распараллеливанием с 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-соединения с системными устройствами).
Я знаю, что это чрезвычайно сложно. Статья Кантера также показывает некоторые части этого, чтобы рассказать о внешнем интерфейсе, например, отдельно от исполнительных блоков или кэшей.