Определение конвейера; конфликты при конвейерном исполнении машинного кода; особенности обработки конфликтов в CICS- и RISC-архитектурах; пример генерации машинного кода для CISC-процессора ARM и RISC-процессора MIPS для демонстрации приёмов обработки потенциальных конфликтов конвейеризации на уровне компиляции; параллельное исполнение команд на примере процессоров Intel x86
Команды процессора часто задействуют значительное количество блоков процессора и исполняются за несколько тактов. Например, рассмотрим команду сложения числа из регистра с числом из памяти dd eax, DWORD PTR [ebp-0x8] в процессоре Intel 80386. Эта команда выполняется следующим образом:
ebp и константа -0x8 подаются на вход адресному сумматору (заметим, что здесь речь не о сумматоре, входящем в состав АЛУ, а об отдельном адресном сумматоре, предназначенном для арифметических действий над адресами; различных специализированных сумматоров в процессоре может быть достаточно много);eax.Таким образом эта команда выполняется за 6 тактов (следует отметить, что этот пример существенно упрощён для наглядности). При этом, если не использовать дополнительных оптимизаций, то на каждом такте задействуется лишь часть блоков процессора, а остальные простаивают. Например, при сложении двух значений, которые находятся в регистрах процессора, блок работы с памятью бездействует.
Конвейер - это механизм, предназначенный для распараллеливания выполнения команд программы между блоками процессора. Он позволяет загрузить блоки процессора при выполнении команд оптимально, без простоев.
Приведём пример конвейера. Рассмотрим упрощённый процессор, у которого каждая команда исполняется в три этапа, точнее, за три такта:
Результаты выполнения команд используются следующими командами программы. Посмотрим, как это простое соображение согласуется с конвейером.
Приведённый выше пример показывает, что Команда 3 не сможет использовать результаты Команды 2, поскольку свои входные данные она читает раньше, чем Команда 2 записывает свои результаты. В том случае, когда Команде 3 действительно нужны результаты Команды 2, Команда 2 и Команда 3 называются зависимыми.
Ситуации, возникающие при конвейеризованном исполнении команд, которые препятствуют корректному выполнению очередной команды, называются конфликтами.
Конфликты бывают следующих видов.
Поскольку конфликты по данным являются распространённой на практике проблемой, приведём ряд примеров таких конфликтов:
Сделаем замечание относительно третьего вида конфликтов (по управлению) отметим. Такие конфликты дополнительно осложняются тем обстоятельством, что заранее не ясно, в какое место программы произойдёт условный переход, и поэтому не понятно, из какой его ветки загружать на конвейер следующие команды. В связи с этим во многих процессорах есть система предсказания условных переходов, которая собирает статистику по переходам и выбирает наиболее вероятный вариант. В случае ошибки предсказания конвейер очищается от частично обработанных команд, что вызывает задержки.
Для того чтобы программа могла работать корректно с применением конвейера, конфликтные ситуации требуется обрабатывать, то есть обнаруживать и разрешать. Обнаружение конфликтов оказывается непростой задачей, которую мы не будем здесь рассматривать. Разрешение конфликтов может выполняться различными способами. Самым простым способом является "торможение" конвейера (pipeline stall) с целью задержки выполнения следующей команды до момента исчерпания конфликта. Но, во-первых, конфликтную ситуацию требуется обнаружить, что не просто, во-вторых, задержки уменьшают преимущества конвейера.
Выделим следующие подходы к обработке конфликтов.
NOP (No Operation). Команда NOP ничего не делает, но замедляет работу программы на один такт, в некоторых процессорах - и на большее число тактов.Intel 80486), а также самостоятельно переупорядочивает команды, чтобы исключить конфликты с минимизацией потери времени (так действуют процессор Intel Pentium и последующие процессоры семейств Intel x86).Рассмотрев общие подходы к обработке конфликтов на конвейерах, остановимся теперь на особенностях этой процедуры в RISC- и CISC-процессорах. Свойства машинного языка оказывают определяющее влияние на то, каким образом можно организовать конвейерное исполнение машинного кода. Как следствие, различия между CISC- и RISC-процессорами приводят к использованию различных подходов к обработке конфликтов.
CISC-процессоры делают акцент на динамической обработке конфликтов, поскольку в CISC-процессорах команды имеют различную сложность и время работы, задействуют разные блоки процессора и обращаются к операндам различного вида. Кроме того, в некоторых семействах динамическая обработка конфликтов является единственным возможным решением, поскольку механизм конвейеризации в них появился не сразу, и требовалась совместимость с предыдущими версиями процессоров, то есть механизм конвейеризации не является глубоко интегрированным в процессор.
Рассмотрим пример. Первым конвейеризованным процессором семейства Intel x86 был процессор Intel 80486. Для того, чтобы у него была возможность корректно и по возможности быстро исполнять машинный код, предназначенный для предыдущих версий не конвейеризованных процессоров, ничего не оставалось, кроме как "научиться" разрешать конфликты динамически. Ни на какие "подсказки" компилятора он при этом рассчитывать не мог, ведь существующий машинный код для предыдущих процессоров семейства Intel x86 про конвейеризацию ничего не знал.
Реализация динамической обработки конфликтов в CISC-процессорах требует значительного усложнения конвейера. Для сравнения можно представить себе, как усложнился бы конвейер на машиностроительном заводе, если бы на нём приходилось одновременно собирать бульдозеры, комбайны и танки, вдобавок разных моделей. Отметим, что иногда компиляторы и низкоуровневые программисты всё же помогают CISC-процессорам, создавая максимально "безконфликтный" машинный код. Это положительно сказывается на скорости работы программ на старых процессорах, которые, как, например, Intel 80486, умеют при обнаружении конфликтов лишь задерживать выполнение команд, но не переупорядочивать их.
Машинный код RISC-процессоров, с точки зрения способа хранения команд в оперативной памяти, оказывается проще, чем у CISC-процессоров. Команды RISC-процессоров выполняются за одинаковое число тактов, а более простой машинный язык упрощает обнаружение и обработку конфликтов. Некоторые современные RISC-процессоры (например, процессоры семейства ARM), также как и CISC-процессоры, обнаруживают конфликты и могут переупорядочивать исполнение команд, минимизируя таким образом потерю производительности из-за конфликтов. Другие же RISC-процессоры вовсе не обрабатывают конфликты, например, процессоры семейства MIPS (название семейства этих процессоров расшифровывается как Microprocessor without Interlocked Pipelined Stages, то есть микропроцессор без блокировок на конвейере). Такие процессоры требуют, чтобы подготовленный для них машинный код не содержал потенциальных конфликтов при конвейеризации. Эту "бесконфликтность" машинного кода обеспечивает компилятор.
| C-программа | Трансляция для ARM | Трансляция для MIPS | ||
|---|---|---|---|---|
| Результат | Комментарий | Результат | Комментарий | |
int a,b;
|
ldr r3, [fp, #-8]; ldr r2, [fp, #-12]
|
Загрузка переменных: а - в регистр r3, b - в регистр r2. |
lw $3,8($fp); lw $2,12($fp)
|
Загрузка переменных: а - в регистр $3, b - в регистр $2. |
if (a>b)
|
Nop
|
Конфликт по данным: необходимо доп. время для окончания загрузки переменной b в регистр $2, чтобы использовать его в след. команде |
||
cmp r3, r2
|
Сравнение регистров r3 и r2
|
slt $2,$2,$3
|
Если $2< $3, загрузить 1 в регистр $2, иначе загрузить 0 в регистр $2
|
|
ble .L2
|
Условный переход (если результат последнего сравнения был " ") на следующую за оператором if команду |
beq $2,$0,$L2
|
Условный переход (если $2 = $0) на следующую за оператором if команду. Спец. регистр $0 всегда содержит значение 0
|
|
Nop
|
Конфликт по управлению: эта команда всегда попадает на конвейер до того, как становится известно, выполнит ли предыдущая команда переход | |||
ldr r2, [fp, #-12]
|
Загрузка переменной b в регистр r2
|
lw $2,12($fp)
|
Загрузка переменной b в регистр 2 |
|
nop
|
Конфликт по данным: необходимо доп. время для окончания загрузки переменной b в регистр $2, чтобы использовать его в след. команде |
|||
str r2, [fp, #-8]
|
Выгрузка регистра r2 в переменную a
|
sw $2,8($fp)
|
Выгрузка регистра $2 в переменную a
|
|
b = 0;
|
L2:
|
Метка .L2 - адрес следующей за оператором if команды |
$L2:
|
Метка $L2 - адрес следующей за оператором if команды |
mov r2, #0; str r2, [fp, #-12]
|
Загрузка 0 в регистр r2, а следом выгрузка из регистра r2 в переменную b
|
sw $0,12($fp)
|
Выгрузка из спец. регистра $0 (всегда содержит 0) в переменную b
|
|
Как уже многократно указывалось, повышение производительности современных процессоров осуществляется в значительной степени за счёт распараллеливания. Одним из популярных способов распараллеливания является конвейеризация. Но хотя конвейеризация и позволяет значительно ускорить исполнение программ, максимального эффекта она достигает лишь в идеальных ситуациях, когда конфликты не возникают, или когда удаётся переупорядочить команды таким образом, чтобы эти конфликты исчерпать. Для дальнейшего повышения производительности применяются (иногда в сочетании с конвейеризацией) более сложные решения.
В качестве первой техники параллельного исполнения команд процессором рассмотрим суперскалярность - возможность одновременно выполнять несколько машинных команд за счёт наличия в процессоре нескольких однотипных функциональных блоков (арифметико-логических устройств, математических сопроцессоров и т.д.) В семействе Intel x86 первым процессором, где была реализована суперскалярность, был процессор Intel Pentium (1993 год). Этот процессор содержал два арифметико-логических устройства, которые позволяли исполнять одновременно две соседние команды, если они не зависели друг от друга. При этом независимые команды одновременно обрабатывались двумя разными конвейерами. Для этого компиляторы стремились генерировать машинный код, соседние команды которого не зависели бы друг от друга.
Более продвинутой техникой параллельного исполнения машинных команд является внеочередное исполнение (Out of Order Execution). Эта техника позволяет исполнять команды программы не в порядке следования, а в порядке готовности к выполнению. При использовании внеочередного исполнения некоторое множество команд программы исполняется тогда, когда для всех этих команд готовы нужные им входные данные. Первой ЭВМ, в которой был реализован механизм внеочередного исполнения команд, был суперкомпьютер CDC 6600 компании Cray Research, созданный в 1963 году.
Приведём следующий пример. Пусть в программе подряд идут следующие команды:
(1) a = b / c (2) d = b + a (3) x = y * z
Деление двух чисел (команда (1)) является сложной командой, которая выполняется существенно дольше, чем, например, сложение. Вспомните алгоритм деления "в столбик": в нём последовательно выполняется много операций и проверок. Процессор выполняет деление практически так же, но только в двоичной системе. В то же время, от результатов команды (1) зависит выполнение команды (2), а выполнение команды (3) не зависит. Поэтому можно приступить к выполнению команд (1) и (3) одновременно, а команду (2) начать выполнять после вычисления значения a.
Рассмотрим общий сценарий внеочередного исполнения команд в современных процессорах на примере распространённого известного процессора Intel Core i7. После выборки процессором очередной команды для исполнения эта команда делится на микрооперации - простые действия, такие как "сложить два числа", "записать значение в регистр", "выдать адрес ячейки на шину адреса" и подобные им. Микрооперации помещаются в специальный буфер переупорядочивания (Reorder Buffer), в котором они упорядочиваются так, чтобы, с одной стороны, не нарушить корректность программы, а с другой стороны оптимально загрузить блоки процессора. Причём в буфере находятся и переупорядочиваются одновременно микрооперации нескольких идущих подряд команд. Чтобы получить больше свободы для переупорядочивания команд, процессор выполняет переименование регистров: для разных микроопераций, работающих с одними и теми же регистрами, назначаются новые регистры. Получившиеся микрооперации выполняются шестью блоками процессора - тремя АЛУ и тремя блоками доступа к памяти. При обнаружении условного перехода одновременно начинают обрабатываться обе ветви then и else, то есть используется спекулятивное исполнение программы. Но после фактического вычисления условия фиксируются только результаты команд из выбранной в переходе ветки.
Компания Intel реализовала внеочередное исполнение команд задолго до запуска в производство современных многоядерных процессоров, в рамках процессоров Intel Pentium Pro в начале 2000-х годов. Было замечено, что блоки процессора выполняли микрооперации эффективно, но часто простаивали, ожидая результатов выборки машинных команд из оперативной памяти. Для того, чтобы оптимально загрузить эти блоки, была создана технология HyperThreading. Она выбирала машинные команды из оперативной памяти в двух параллельных потоках. Одноядерный процессор с применением технологии HyperThreading, фактически, начинает работать, как двухъядерный. Технология HyperThreading была запатентована сотрудником компании Sun Microsystems в 1994 году, но впервые была реализована в лишь 2002 году (Intel).
NOP для разрешения конфликтов.CICS-процессоров и почему?RICS-процессоров и почему?CISC-процессоры разрешают конфликты конвейера?RISC-процессорах?RISC-процессоров проще, чем CISC?HyperThreading.Определение конвейера; конфликты при конвейерном исполнении машинного кода; особенности обработки конфликтов в CICS- и RISC-архитектурах; пример генерации машинного кода для CISC-процессора ARM и RISC-процессора MIPS для демонстрации приёмов обработки потенциальных конфликтов конвейеризации на уровне компиляции; параллельное исполнение команд на примере процессоров Intel x86
Команды процессора часто задействуют значительное количество блоков процессора и исполняются за несколько тактов. Например, рассмотрим команду сложения числа из регистра с числом из памяти dd eax, DWORD PTR [ebp-0x8] в процессоре Intel 80386. Эта команда выполняется следующим образом:
ebp и константа -0x8 подаются на вход адресному сумматору (заметим, что здесь речь не о сумматоре, входящем в состав АЛУ, а об отдельном адресном сумматоре, предназначенном для арифметических действий над адресами; различных специализированных сумматоров в процессоре может быть достаточно много);eax.Таким образом эта команда выполняется за 6 тактов (следует отметить, что этот пример существенно упрощён для наглядности). При этом, если не использовать дополнительных оптимизаций, то на каждом такте задействуется лишь часть блоков процессора, а остальные простаивают. Например, при сложении двух значений, которые находятся в регистрах процессора, блок работы с памятью бездействует.
Конвейер - это механизм, предназначенный для распараллеливания выполнения команд программы между блоками процессора. Он позволяет загрузить блоки процессора при выполнении команд оптимально, без простоев.
Приведём пример конвейера. Рассмотрим упрощённый процессор, у которого каждая команда исполняется в три этапа, точнее, за три такта:
Результаты выполнения команд используются следующими командами программы. Посмотрим, как это простое соображение согласуется с конвейером.
Приведённый выше пример показывает, что Команда 3 не сможет использовать результаты Команды 2, поскольку свои входные данные она читает раньше, чем Команда 2 записывает свои результаты. В том случае, когда Команде 3 действительно нужны результаты Команды 2, Команда 2 и Команда 3 называются зависимыми.
Ситуации, возникающие при конвейеризованном исполнении команд, которые препятствуют корректному выполнению очередной команды, называются конфликтами.
Конфликты бывают следующих видов.
Поскольку конфликты по данным являются распространённой на практике проблемой, приведём ряд примеров таких конфликтов:
Сделаем замечание относительно третьего вида конфликтов (по управлению) отметим. Такие конфликты дополнительно осложняются тем обстоятельством, что заранее не ясно, в какое место программы произойдёт условный переход, и поэтому не понятно, из какой его ветки загружать на конвейер следующие команды. В связи с этим во многих процессорах есть система предсказания условных переходов, которая собирает статистику по переходам и выбирает наиболее вероятный вариант. В случае ошибки предсказания конвейер очищается от частично обработанных команд, что вызывает задержки.
Для того чтобы программа могла работать корректно с применением конвейера, конфликтные ситуации требуется обрабатывать, то есть обнаруживать и разрешать. Обнаружение конфликтов оказывается непростой задачей, которую мы не будем здесь рассматривать. Разрешение конфликтов может выполняться различными способами. Самым простым способом является "торможение" конвейера (pipeline stall) с целью задержки выполнения следующей команды до момента исчерпания конфликта. Но, во-первых, конфликтную ситуацию требуется обнаружить, что не просто, во-вторых, задержки уменьшают преимущества конвейера.
Выделим следующие подходы к обработке конфликтов.
NOP (No Operation). Команда NOP ничего не делает, но замедляет работу программы на один такт, в некоторых процессорах - и на большее число тактов.Intel 80486), а также самостоятельно переупорядочивает команды, чтобы исключить конфликты с минимизацией потери времени (так действуют процессор Intel Pentium и последующие процессоры семейств Intel x86).Рассмотрев общие подходы к обработке конфликтов на конвейерах, остановимся теперь на особенностях этой процедуры в RISC- и CISC-процессорах. Свойства машинного языка оказывают определяющее влияние на то, каким образом можно организовать конвейерное исполнение машинного кода. Как следствие, различия между CISC- и RISC-процессорами приводят к использованию различных подходов к обработке конфликтов.
CISC-процессоры делают акцент на динамической обработке конфликтов, поскольку в CISC-процессорах команды имеют различную сложность и время работы, задействуют разные блоки процессора и обращаются к операндам различного вида. Кроме того, в некоторых семействах динамическая обработка конфликтов является единственным возможным решением, поскольку механизм конвейеризации в них появился не сразу, и требовалась совместимость с предыдущими версиями процессоров, то есть механизм конвейеризации не является глубоко интегрированным в процессор.
Рассмотрим пример. Первым конвейеризованным процессором семейства Intel x86 был процессор Intel 80486. Для того, чтобы у него была возможность корректно и по возможности быстро исполнять машинный код, предназначенный для предыдущих версий не конвейеризованных процессоров, ничего не оставалось, кроме как "научиться" разрешать конфликты динамически. Ни на какие "подсказки" компилятора он при этом рассчитывать не мог, ведь существующий машинный код для предыдущих процессоров семейства Intel x86 про конвейеризацию ничего не знал.
Реализация динамической обработки конфликтов в CISC-процессорах требует значительного усложнения конвейера. Для сравнения можно представить себе, как усложнился бы конвейер на машиностроительном заводе, если бы на нём приходилось одновременно собирать бульдозеры, комбайны и танки, вдобавок разных моделей. Отметим, что иногда компиляторы и низкоуровневые программисты всё же помогают CISC-процессорам, создавая максимально "безконфликтный" машинный код. Это положительно сказывается на скорости работы программ на старых процессорах, которые, как, например, Intel 80486, умеют при обнаружении конфликтов лишь задерживать выполнение команд, но не переупорядочивать их.
Машинный код RISC-процессоров, с точки зрения способа хранения команд в оперативной памяти, оказывается проще, чем у CISC-процессоров. Команды RISC-процессоров выполняются за одинаковое число тактов, а более простой машинный язык упрощает обнаружение и обработку конфликтов. Некоторые современные RISC-процессоры (например, процессоры семейства ARM), также как и CISC-процессоры, обнаруживают конфликты и могут переупорядочивать исполнение команд, минимизируя таким образом потерю производительности из-за конфликтов. Другие же RISC-процессоры вовсе не обрабатывают конфликты, например, процессоры семейства MIPS (название семейства этих процессоров расшифровывается как Microprocessor without Interlocked Pipelined Stages, то есть микропроцессор без блокировок на конвейере). Такие процессоры требуют, чтобы подготовленный для них машинный код не содержал потенциальных конфликтов при конвейеризации. Эту "бесконфликтность" машинного кода обеспечивает компилятор.
| C-программа | Трансляция для ARM | Трансляция для MIPS | ||
|---|---|---|---|---|
| Результат | Комментарий | Результат | Комментарий | |
int a,b;
|
ldr r3, [fp, #-8]; ldr r2, [fp, #-12]
|
Загрузка переменных: а - в регистр r3, b - в регистр r2. |
lw $3,8($fp); lw $2,12($fp)
|
Загрузка переменных: а - в регистр $3, b - в регистр $2. |
if (a>b)
|
Nop
|
Конфликт по данным: необходимо доп. время для окончания загрузки переменной b в регистр $2, чтобы использовать его в след. команде |
||
cmp r3, r2
|
Сравнение регистров r3 и r2
|
slt $2,$2,$3
|
Если $2< $3, загрузить 1 в регистр $2, иначе загрузить 0 в регистр $2
|
|
ble .L2
|
Условный переход (если результат последнего сравнения был " ") на следующую за оператором if команду |
beq $2,$0,$L2
|
Условный переход (если $2 = $0) на следующую за оператором if команду. Спец. регистр $0 всегда содержит значение 0
|
|
Nop
|
Конфликт по управлению: эта команда всегда попадает на конвейер до того, как становится известно, выполнит ли предыдущая команда переход | |||
ldr r2, [fp, #-12]
|
Загрузка переменной b в регистр r2
|
lw $2,12($fp)
|
Загрузка переменной b в регистр 2 |
|
nop
|
Конфликт по данным: необходимо доп. время для окончания загрузки переменной b в регистр $2, чтобы использовать его в след. команде |
|||
str r2, [fp, #-8]
|
Выгрузка регистра r2 в переменную a
|
sw $2,8($fp)
|
Выгрузка регистра $2 в переменную a
|
|
b = 0;
|
L2:
|
Метка .L2 - адрес следующей за оператором if команды |
$L2:
|
Метка $L2 - адрес следующей за оператором if команды |
mov r2, #0; str r2, [fp, #-12]
|
Загрузка 0 в регистр r2, а следом выгрузка из регистра r2 в переменную b
|
sw $0,12($fp)
|
Выгрузка из спец. регистра $0 (всегда содержит 0) в переменную b
|
|
Как уже многократно указывалось, повышение производительности современных процессоров осуществляется в значительной степени за счёт распараллеливания. Одним из популярных способов распараллеливания является конвейеризация. Но хотя конвейеризация и позволяет значительно ускорить исполнение программ, максимального эффекта она достигает лишь в идеальных ситуациях, когда конфликты не возникают, или когда удаётся переупорядочить команды таким образом, чтобы эти конфликты исчерпать. Для дальнейшего повышения производительности применяются (иногда в сочетании с конвейеризацией) более сложные решения.
В качестве первой техники параллельного исполнения команд процессором рассмотрим суперскалярность - возможность одновременно выполнять несколько машинных команд за счёт наличия в процессоре нескольких однотипных функциональных блоков (арифметико-логических устройств, математических сопроцессоров и т.д.) В семействе Intel x86 первым процессором, где была реализована суперскалярность, был процессор Intel Pentium (1993 год). Этот процессор содержал два арифметико-логических устройства, которые позволяли исполнять одновременно две соседние команды, если они не зависели друг от друга. При этом независимые команды одновременно обрабатывались двумя разными конвейерами. Для этого компиляторы стремились генерировать машинный код, соседние команды которого не зависели бы друг от друга.
Более продвинутой техникой параллельного исполнения машинных команд является внеочередное исполнение (Out of Order Execution). Эта техника позволяет исполнять команды программы не в порядке следования, а в порядке готовности к выполнению. При использовании внеочередного исполнения некоторое множество команд программы исполняется тогда, когда для всех этих команд готовы нужные им входные данные. Первой ЭВМ, в которой был реализован механизм внеочередного исполнения команд, был суперкомпьютер CDC 6600 компании Cray Research, созданный в 1963 году.
Приведём следующий пример. Пусть в программе подряд идут следующие команды:
(1) a = b / c (2) d = b + a (3) x = y * z
Деление двух чисел (команда (1)) является сложной командой, которая выполняется существенно дольше, чем, например, сложение. Вспомните алгоритм деления "в столбик": в нём последовательно выполняется много операций и проверок. Процессор выполняет деление практически так же, но только в двоичной системе. В то же время, от результатов команды (1) зависит выполнение команды (2), а выполнение команды (3) не зависит. Поэтому можно приступить к выполнению команд (1) и (3) одновременно, а команду (2) начать выполнять после вычисления значения a.
Рассмотрим общий сценарий внеочередного исполнения команд в современных процессорах на примере распространённого известного процессора Intel Core i7. После выборки процессором очередной команды для исполнения эта команда делится на микрооперации - простые действия, такие как "сложить два числа", "записать значение в регистр", "выдать адрес ячейки на шину адреса" и подобные им. Микрооперации помещаются в специальный буфер переупорядочивания (Reorder Buffer), в котором они упорядочиваются так, чтобы, с одной стороны, не нарушить корректность программы, а с другой стороны оптимально загрузить блоки процессора. Причём в буфере находятся и переупорядочиваются одновременно микрооперации нескольких идущих подряд команд. Чтобы получить больше свободы для переупорядочивания команд, процессор выполняет переименование регистров: для разных микроопераций, работающих с одними и теми же регистрами, назначаются новые регистры. Получившиеся микрооперации выполняются шестью блоками процессора - тремя АЛУ и тремя блоками доступа к памяти. При обнаружении условного перехода одновременно начинают обрабатываться обе ветви then и else, то есть используется спекулятивное исполнение программы. Но после фактического вычисления условия фиксируются только результаты команд из выбранной в переходе ветки.
Компания Intel реализовала внеочередное исполнение команд задолго до запуска в производство современных многоядерных процессоров, в рамках процессоров Intel Pentium Pro в начале 2000-х годов. Было замечено, что блоки процессора выполняли микрооперации эффективно, но часто простаивали, ожидая результатов выборки машинных команд из оперативной памяти. Для того, чтобы оптимально загрузить эти блоки, была создана технология HyperThreading. Она выбирала машинные команды из оперативной памяти в двух параллельных потоках. Одноядерный процессор с применением технологии HyperThreading, фактически, начинает работать, как двухъядерный. Технология HyperThreading была запатентована сотрудником компании Sun Microsystems в 1994 году, но впервые была реализована в лишь 2002 году (Intel).
NOP для разрешения конфликтов.CICS-процессоров и почему?RICS-процессоров и почему?CISC-процессоры разрешают конфликты конвейера?RISC-процессорах?RISC-процессоров проще, чем CISC?HyperThreading.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.