Как работает аппаратное распараллеливание?
Попробую рассказать.
Шаг 1. Конвейерная архитектура (Pentium 1).
Чем-то напоминает многоствольный пулемёт. Один патрон заряжается, другой выстреливается, третий выбрасывается. Стоп… Одна команда выбирается, вторая декодируется, третья и четвёртая исполняются.
Шаг 2. Суперскалярность (Pentium то ли Pro, то ли MMX).
У нас есть несколько исполнительных блоков (в данном случае целочисленных). Если команды не противоречат друг другу, их можно пустить на обоих блоках параллельно.
Шаг 3. Микрокоманды и VLIW (если не ошибаюсь, из x86 первым реализован в Transmeta Crusoe).
Состоит из таких этапов.
1. Разбиваем операции x86 на микрокоманды — например, «перебросить из eax в сумматор», «сдвинуть содержимое сумматора на 1 вправо»…
2. Собираем это самое «очень длинное слово операции» из микрокоманд, заботясь о том, чтобы не было зависимостей по данным. Один сумматор получает слово из eax, а параллельно второй сдвигает на 1. Каждый из разрядов «длинного слова» управляет своим блоком процессора: сумматором, памятью, вводом-выводом…
3. Ну и исполняем это самое слово.
Всем этим архитектурам важно т.н. предсказание ветвлений. Чтобы всё это дело работало, нужно, чтобы несколько операций были декодированы наперёд. Проблема — ветвление: если мы не угадаем, случилось ли ветвление, вся предварительная работа насмарку. В микроконтроллерах с коротким конвейером и предсказуемым до такта временем работы мы на это чхаем: например, в инструкции к AVR говорится, что там двухместный конвейер: один такт декодирование и один (два, три) — исполнение. Обычно такт на декодирование не важен (и не указан в инструкции), но мы теряем его, если случился переход.
А в x86 алгоритмы предсказания ветвлений довольно сложны.
ЗЫ. В суперскалярных процессорах несколько десятков регистров, на которые динамически навешиваются ярлыки: сейчас EAX=r5, а через две команды — уже r13. Так называемое «переименование регистров».
ну вот представьте. Есть у вас поток команд. Скажем подряд идут команды в духе "сложи мне два числа", "скопируй значение из памяти в регистры", "пошли что-то на шину что бы к девайсу ушло". Для всех этих трех операций нужны разные штуки. Для первой - свободный АЛУ, для второго - управляющие сигналы на оперативку послать, для третьего - третье. И все эти операции занимают сильно по разному времени.
Вывод - мы можем отсортировать команды на этапе конвееризации процессора и таким образом отправлять на обработку команды по мере доступности ресурсов.
Или например... есть у нас у ядра процессора аж 4 АЛУ. То есть мы по хорошему можем сразу 4 арефмитические операции выполнять. Можно распаралелить если команды подряд идут.
Вообще тут лучше почитать по конвееризации процессоров. Это весьма жирная тема.Как операционная система выбирает ядро для исполнения
Почитайте про планировщики, например вот: Process Scheduling in linux