Сообщение Re[14]: А если бы все с начала ? от 01.02.2018 8:16
Изменено 03.01.2022 8:17 netch80
Re[14]: А если бы все с начала ?
Здравствуйте, gardener, Вы писали:
N>>Не-а. Он и за прошедшие 20 лет не решил эти проблемы, и не решил бы их и тогда.
N>>Проблемы Itanium в том, что EPIC не работает с современной памятью и кэшами в мультипроцессорной системе.
G>А можно больше деталей?
Можно.
Пусть у вас последовательность команд. Какая-то часть из них кодирует операции только с регистрами — считаем, они доступны всегда, и на чтение и на запись. Какая-то обращается к памяти. Вот тут начинаются проблемы. Если у вас обыкновенная DRAM, у неё самая длительная операция это закрыть неактуальную строку (переписать из её кэша, который в самой микросхеме DRAM, в собственно DRAM-часть) и открыть заказанную (прочитать и переписать в тот кэш на борту DRAM). Эта операция в лучших представителях занимает около 25 нс, в "бюджетных" DDR4 до 40 нс. (Обозначается обычно tRAS или похоже.) В тактах процессора... ну умножьте на 4, если у него 4ГГц тактовая. А ещё надо добавить время на опознание, что данных нет ни в одном локальном кэше (обычно добавляется где-то до 30 тактов), нет ни у одного партнёра по SMP (может быть и ~50 тактов) и на чтение полной строки (64 байта на x86) из DRAM (ну, ещё десятка два тактов, там высокая параллельность) — спокойно можно добрать и до 300 тактов.
Это чрезвычайно высокая цифра, даже если сравнивать с супердорогими арифметическими операциями типа деления (128/64 на x86, считается, до 90 тактов).
Теперь представим себе исполнение этих команд команд. На x86 вполне может быть, что пока 30-я по счёту только-только прочиталась из памяти, 25-я раскодирована, 20-й назначили все внутренние регистры и она ждёт выполнения, 15-я выполняется, 10-я закончила и результаты готовы, но ждёт, пока предыдущие завершат операции, результат 7-й сидит во write queue на выходе в кэш, для 5-й заканчивают синхронизацию между кэшами в SMP, 1-я наконец всё типа закончила (слила результат в кэш в строку, которая эксклюзивно принадлежит на запись текущему ядру). В это время 2-я задумчиво занимается делением, и её результаты будут ещё тактов через 40. Но она (2-я) пишет в регистр, поэтому не тормозит заметную остальных; её результаты будут применены только к 15-й команде, а результаты той — только к 24-й, поэтому с остальными можно работать. Я не преувеличиваю в цифрах — у Skylake, например, цепочка от "наконец заканчиваем, фиксируем результаты" до "выбрали, начинаем декодировать" может быть до 224 микроопераций.
Процессор сам вычисляет, какая команда от какой зависит. Есть очевидные зависимости по регистрам (типа, если 3-я записала в eax, а 5-я его читает, 5-я должна выполняться после 3-й). Есть зависимости по памяти (x86 настаивает на том, что все операции записи в память упорядочены по этим действиям записи — хотя вычисления в них не требуют такой зависимости). Получается такой себе DAG исполнений, внутри которого заметная свобода.
А теперь чем отличается EPIC? Само название поясняет: Explicitly parallel instruction computing. Параллельность рассчитана на этапе написания машинного кода (обычно — компилятором). Причём не в терминах "данная команда хочет результаты той, что на 15 раньше по цепочке" — такое бы требовало слишком много места для записи — а в виде группировок типа "данные команды друг на друга не влияют" (см. ниже), "можно параллелить сколько угодно по вкусу" и "а вот тут мы знаем явную зависимость, надо завершить все предыдущие до всех последующих". В доке по Itanium это выглядит так:
>> An instruction group is a sequence of instructions starting at a given bundle address and slot number and including all instructions at sequentially increasing slot numbers and bundle addresses up to the first stop, taken branch, Break Instruction fault due to a break.b, or Illegal Operation fault due to a Reserved or Reserved if PR[qp] is one encoding in the B-type opcode space. For the instructions in an instruction group to have well-defined behavior, they must meet the ordering and dependency requirements described below.
и вот главные слова — "must meet the ordering and dependency requirements". Автору машкода надо явно определить группы, в которых взаимовлияние минимизировано, и зафиксировать их. Дальше в доке много страшных слов, но вот одни из ключевых:
>> Between instruction groups, every instruction in a given instruction group will behave as though its read occurred after the update of all the instructions from the previous instruction group.
Процессор не имеет права посчитать, что какая-то команда из IG1 может быть выполнена одновременно с какой-то последующей IG2, если между ними стоит явный stop. Даже прочесть данные из памяти в регистр — потому что это называется update of architectural state. Память тормозит? Жди.
>> Within an instruction group, every instruction will behave as though its read of the register state occurred before the update of the register state by any instruction (prior or later) in that instruction group, except as noted in the Register dependencies and Memory dependencies described below.
[...]
>> Register dependencies: Within an instruction group, read-after-write (RAW) and write-after-write (WAW) register dependencies are not allowed (except пара незначительных исключений)
А это, наоборот, в одной группе не может быть зависимостей (как уже сказал — надо отбирать только независимые действия). Хотел исхитриться и применить результаты чтения сразу же? Обломись, бабка, мы на корабле.
Дальше в доке много чего — 100500 поправок, уточнений и исключений, словно специально, чтобы запутать всех и усложнить компилятор до предела. Но смысл основной тот же: пока на тех архитектурах, где процессор вычисляет зависимости на ходу (как x86), одна команда не тормозит соседних "товарок", пока её результаты не нужны, и может быть выделена из основного потока — на IA-64 такой свободы нет, учись предсказывать задержки по памяти — то, чего в принципе предсказать невозможно на обычном современном железе (если вы не занимаетесь Meltdownʼом).
Резонный вопрос — а почему вообще Intel решил, что возможно такую архитектуру сделать эффективной? А вот тут надо заглянуть в историю и заметить, что тогда же его топ-менеджмент попался на две удочки одновременно — первая под названием Rambus, а вторая — NetBurst (Pentium 4 должен был по тем планам стать последним x86). Супербыстрая DRAM плюс ориентация на потоковые SIMD действия (для "мультимедиа") и пренебрежение всеми остальными классами задач. Чем тут поможет SIMD? А именно тем, что для основных задач хорошо предсказывается необходимость подчитать память — можно заранее (на одну-две IG) сказать prefetch на нужный кусок, пока выполняются предыдущие, оно уже в L1. Но тут наступил облом — не стала мультимедия единственным использованием компьютера, а Rambus мало того, что выпустила память с бо́льшими задержками, так и оказалось, что все новомодные усовершенствования это фикция, а заодно патентный буллинг (Intel потеряла ок. 4e8$$, насколько помню).
Так что EPIC эффективен только там, где вы можете строго ограничить время одной даже самой длительной команды. Видимо, из подобных соображений Intel исключил из IA-64 простое целочисленное деление — он знал, что это долго, но "не знал" того же про чтение DRAM. Если у вас SRAM (умножьте цену памяти на 10-20) — ok, вперёд. Если у вас DSP (тоже, считаем, SRAM) — тоже пойдёт, туда подобные архитектуры внедрились и устоялись. Но для "обычного" современного компа, для толстого сервера — ой, зась.
И вслед этому очевидный вопрос про "импортозаместительный" Эльбрус-4. На синтетических тестах он много чего показывает, но про реальные задачи идёт много подпольных отзывов про жуткие тормоза...
N>>Не-а. Он и за прошедшие 20 лет не решил эти проблемы, и не решил бы их и тогда.
N>>Проблемы Itanium в том, что EPIC не работает с современной памятью и кэшами в мультипроцессорной системе.
G>А можно больше деталей?
Можно.
Пусть у вас последовательность команд. Какая-то часть из них кодирует операции только с регистрами — считаем, они доступны всегда, и на чтение и на запись. Какая-то обращается к памяти. Вот тут начинаются проблемы. Если у вас обыкновенная DRAM, у неё самая длительная операция это закрыть неактуальную строку (переписать из её кэша, который в самой микросхеме DRAM, в собственно DRAM-часть) и открыть заказанную (прочитать и переписать в тот кэш на борту DRAM). Эта операция в лучших представителях занимает около 25 нс, в "бюджетных" DDR4 до 40 нс. (Обозначается обычно tRAS или похоже.) В тактах процессора... ну умножьте на 4, если у него 4ГГц тактовая. А ещё надо добавить время на опознание, что данных нет ни в одном локальном кэше (обычно добавляется где-то до 30 тактов), нет ни у одного партнёра по SMP (может быть и ~50 тактов) и на чтение полной строки (64 байта на x86) из DRAM (ну, ещё десятка два тактов, там высокая параллельность) — спокойно можно добрать и до 300 тактов.
Это чрезвычайно высокая цифра, даже если сравнивать с супердорогими арифметическими операциями типа деления (128/64 на x86, считается, до 90 тактов).
Теперь представим себе исполнение этих команд команд. На x86 вполне может быть, что пока 30-я по счёту только-только прочиталась из памяти, 25-я раскодирована, 20-й назначили все внутренние регистры и она ждёт выполнения, 15-я выполняется, 10-я закончила и результаты готовы, но ждёт, пока предыдущие завершат операции, результат 7-й сидит во write queue на выходе в кэш, для 5-й заканчивают синхронизацию между кэшами в SMP, 1-я наконец всё типа закончила (слила результат в кэш в строку, которая эксклюзивно принадлежит на запись текущему ядру). В это время 2-я задумчиво занимается делением, и её результаты будут ещё тактов через 40. Но она (2-я) пишет в регистр, поэтому не тормозит заметную остальных; её результаты будут применены только к 15-й команде, а результаты той — только к 24-й, поэтому с остальными можно работать. Я не преувеличиваю в цифрах — у Skylake, например, цепочка от "наконец заканчиваем, фиксируем результаты" до "выбрали, начинаем декодировать" может быть до 224 микроопераций.
Процессор сам вычисляет, какая команда от какой зависит. Есть очевидные зависимости по регистрам (типа, если 3-я записала в eax, а 5-я его читает, 5-я должна выполняться после 3-й). Есть зависимости по памяти (x86 настаивает на том, что все операции записи в память упорядочены по этим действиям записи — хотя вычисления в них не требуют такой зависимости). Получается такой себе DAG исполнений, внутри которого заметная свобода.
А теперь чем отличается EPIC? Само название поясняет: Explicitly parallel instruction computing. Параллельность рассчитана на этапе написания машинного кода (обычно — компилятором). Причём не в терминах "данная команда хочет результаты той, что на 15 раньше по цепочке" — такое бы требовало слишком много места для записи — а в виде группировок типа "данные команды друг на друга не влияют" (см. ниже), "можно параллелить сколько угодно по вкусу" и "а вот тут мы знаем явную зависимость, надо завершить все предыдущие до всех последующих". В доке по Itanium это выглядит так:
>> An instruction group is a sequence of instructions starting at a given bundle address and slot number and including all instructions at sequentially increasing slot numbers and bundle addresses up to the first stop, taken branch, Break Instruction fault due to a break.b, or Illegal Operation fault due to a Reserved or Reserved if PR[qp] is one encoding in the B-type opcode space. For the instructions in an instruction group to have well-defined behavior, they must meet the ordering and dependency requirements described below.
и вот главные слова — "must meet the ordering and dependency requirements". Автору машкода надо явно определить группы, в которых взаимовлияние минимизировано, и зафиксировать их. Дальше в доке много страшных слов, но вот одни из ключевых:
>> Between instruction groups, every instruction in a given instruction group will behave as though its read occurred after the update of all the instructions from the previous instruction group.
Процессор не имеет права посчитать, что какая-то команда из IG1 может быть выполнена одновременно с какой-то последующей IG2, если между ними стоит явный stop. Даже прочесть данные из памяти в регистр — потому что это называется update of architectural state. Память тормозит? Жди.
>> Within an instruction group, every instruction will behave as though its read of the register state occurred before the update of the register state by any instruction (prior or later) in that instruction group, except as noted in the Register dependencies and Memory dependencies described below.
[...]
>> Register dependencies: Within an instruction group, read-after-write (RAW) and write-after-write (WAW) register dependencies are not allowed (except пара незначительных исключений)
А это, наоборот, в одной группе не может быть зависимостей (как уже сказал — надо отбирать только независимые действия). Хотел исхитриться и применить результаты чтения сразу же? Обломись, бабка, мы на корабле.
Дальше в доке много чего — 100500 поправок, уточнений и исключений, словно специально, чтобы запутать всех и усложнить компилятор до предела. Но смысл основной тот же: пока на тех архитектурах, где процессор вычисляет зависимости на ходу (как x86), одна команда не тормозит соседних "товарок", пока её результаты не нужны, и может быть выделена из основного потока — на IA-64 такой свободы нет, учись предсказывать задержки по памяти — то, чего в принципе предсказать невозможно на обычном современном железе (если вы не занимаетесь Meltdownʼом).
Резонный вопрос — а почему вообще Intel решил, что возможно такую архитектуру сделать эффективной? А вот тут надо заглянуть в историю и заметить, что тогда же его топ-менеджмент попался на две удочки одновременно — первая под названием Rambus, а вторая — NetBurst (Pentium 4 должен был по тем планам стать последним x86). Супербыстрая DRAM плюс ориентация на потоковые SIMD действия (для "мультимедиа") и пренебрежение всеми остальными классами задач. Чем тут поможет SIMD? А именно тем, что для основных задач хорошо предсказывается необходимость подчитать память — можно заранее (на одну-две IG) сказать prefetch на нужный кусок, пока выполняются предыдущие, оно уже в L1. Но тут наступил облом — не стала мультимедия единственным использованием компьютера, а Rambus мало того, что выпустила память с бо́льшими задержками, так и оказалось, что все новомодные усовершенствования это фикция, а заодно патентный буллинг (Intel потеряла ок. 4e8$$, насколько помню).
Так что EPIC эффективен только там, где вы можете строго ограничить время одной даже самой длительной команды. Видимо, из подобных соображений Intel исключил из IA-64 простое целочисленное деление — он знал, что это долго, но "не знал" того же про чтение DRAM. Если у вас SRAM (умножьте цену памяти на 10-20) — ok, вперёд. Если у вас DSP (тоже, считаем, SRAM) — тоже пойдёт, туда подобные архитектуры внедрились и устоялись. Но для "обычного" современного компа, для толстого сервера — ой, зась.
И вслед этому очевидный вопрос про "импортозаместительный" Эльбрус-4. На синтетических тестах он много чего показывает, но про реальные задачи идёт много подпольных отзывов про жуткие тормоза...
Re[14]: А если бы все с начала ?
Здравствуйте, gardener, Вы писали:
N>>Не-а. Он и за прошедшие 20 лет не решил эти проблемы, и не решил бы их и тогда.
N>>Проблемы Itanium в том, что EPIC не работает с современной памятью и кэшами в мультипроцессорной системе.
G>А можно больше деталей?
Можно.
Пусть у вас последовательность команд. Какая-то часть из них кодирует операции только с регистрами — считаем, они доступны всегда, и на чтение и на запись. Какая-то обращается к памяти. Вот тут начинаются проблемы. Если у вас обыкновенная DRAM, у неё самая длительная операция это закрыть неактуальную строку (переписать из её кэша, который в самой микросхеме DRAM, в собственно DRAM-часть) и открыть заказанную (прочитать и переписать в тот кэш на борту DRAM). Эта операция занимает, согласно вики на DDR4, минимум 37.5 нс. (DDR3 и выше — в таймингах типа 10-10-10 сложить все 3 числа и подсчитать в тактах в clock rate (не путать с data rate).) В более бюджетных — и выше. Умножьте это на частоту процессора в гигагерцах и получите количество его тактов, сколько он ждёт. А ещё надо добавить время на опознание, что данных нет ни в одном локальном кэше (обычно добавляется где-то до 30 тактов), нет ни у одного партнёра по SMP (может быть и ~50 тактов) и на чтение полной строки (64 байта на x86) из DRAM (ну, ещё десятка два тактов, там высокая параллельность) — спокойно можно добрать и до 300 тактов.
Это чрезвычайно высокая цифра, даже если сравнивать с супердорогими арифметическими операциями типа целочисленного деления (128/64 на x86, считается, до 90 тактов).
Теперь представим себе исполнение этих команд команд. На x86 вполне может быть, что пока 30-я по счёту только-только прочиталась из памяти, 25-я раскодирована, 20-й назначили все внутренние регистры и она ждёт выполнения, 15-я выполняется, 10-я закончила и результаты готовы, но ждёт, пока предыдущие завершат операции, результат 7-й сидит во write queue на выходе в кэш, для 5-й заканчивают синхронизацию между кэшами в SMP, 1-я наконец всё типа закончила (слила результат в кэш в строку, которая эксклюзивно принадлежит на запись текущему ядру). В это время 2-я задумчиво занимается делением, и её результаты будут ещё тактов через 40. Но она (2-я) пишет в регистр, поэтому не тормозит заметную остальных; её результаты будут применены только к 15-й команде, а результаты той — только к 24-й, поэтому с остальными можно работать. Я не преувеличиваю в цифрах — у Skylake, например, цепочка от "наконец заканчиваем, фиксируем результаты" до "выбрали, начинаем декодировать" может быть до 224 микроопераций.
Процессор сам вычисляет, какая команда от какой зависит. Есть очевидные зависимости по регистрам (типа, если 3-я записала в eax, а 5-я его читает, 5-я должна выполняться после 3-й). Есть зависимости по памяти (x86 настаивает на том, что все операции записи в память упорядочены по этим действиям записи — хотя вычисления в них не требуют такой зависимости). Получается такой себе DAG (направленный ациклический граф) исполнений, внутри которого заметная свобода.
А теперь чем отличается EPIC? Само название поясняет: Explicitly parallel instruction computing. Параллельность рассчитана на этапе написания машинного кода (обычно — компилятором). Причём не в терминах "данная команда хочет результаты той, что на 15 раньше по цепочке" — такое бы требовало слишком много места для записи — а в виде группировок типа "данные команды друг на друга не влияют" (см. ниже), "можно параллелить сколько угодно по вкусу" и "а вот тут мы знаем явную зависимость, надо завершить все предыдущие до всех последующих". В доке по Itanium это выглядит так:
>> An instruction group is a sequence of instructions starting at a given bundle address and slot number and including all instructions at sequentially increasing slot numbers and bundle addresses up to the first stop, taken branch, Break Instruction fault due to a break.b, or Illegal Operation fault due to a Reserved or Reserved if PR[qp] is one encoding in the B-type opcode space. For the instructions in an instruction group to have well-defined behavior, they must meet the ordering and dependency requirements described below.
и вот главные слова — "must meet the ordering and dependency requirements". Автору машкода надо явно определить группы, в которых взаимовлияние минимизировано, и зафиксировать их. Дальше в доке много страшных слов, но вот одни из ключевых:
>> Between instruction groups, every instruction in a given instruction group will behave as though its read occurred after the update of all the instructions from the previous instruction group.
Процессор не имеет права посчитать, что какая-то команда из IG1 может быть выполнена одновременно с какой-то последующей IG2, если между ними стоит явный stop. Даже прочесть данные из памяти в регистр — потому что это называется update of architectural state. Память тормозит? Жди.
>> Within an instruction group, every instruction will behave as though its read of the register state occurred before the update of the register state by any instruction (prior or later) in that instruction group, except as noted in the Register dependencies and Memory dependencies described below.
[...]
>> Register dependencies: Within an instruction group, read-after-write (RAW) and write-after-write (WAW) register dependencies are not allowed (except пара незначительных исключений)
А это, наоборот, в одной группе не может быть зависимостей (как уже сказал — надо отбирать только независимые действия). Хотел исхитриться и применить результаты чтения сразу же? Обломись, бабка, мы на корабле.
Дальше в доке много чего — 100500 поправок, уточнений и исключений, словно специально, чтобы запутать всех и усложнить компилятор до предела. Но смысл основной тот же: пока на тех архитектурах, где процессор вычисляет зависимости на ходу (как x86), одна команда не тормозит соседних "товарок", пока её результаты не нужны, и может быть выделена из основного потока — на IA-64 такой свободы нет, учись предсказывать задержки по памяти — то, чего в принципе предсказать невозможно на обычном современном железе (если вы не занимаетесь Meltdownʼом).
Резонный вопрос — а почему вообще Intel решил, что возможно такую архитектуру сделать эффективной? А вот тут надо заглянуть в историю и заметить, что тогда же его топ-менеджмент попался на две удочки одновременно — первая под названием Rambus, а вторая — NetBurst (Pentium 4 должен был по тем планам стать последним x86). Супербыстрая DRAM плюс ориентация на потоковые SIMD действия (для "мультимедиа") и пренебрежение всеми остальными классами задач. Чем тут поможет SIMD? А именно тем, что для основных задач хорошо предсказывается необходимость подчитать память — можно заранее (на одну-две IG) сказать prefetch на нужный кусок, пока выполняются предыдущие, оно уже в L1. Но тут наступил облом — не стала мультимедия единственным использованием компьютера, а Rambus мало того, что выпустила память с бо́льшими задержками, так и оказалось, что все новомодные усовершенствования это фикция, а заодно патентный буллинг (Intel потеряла ок. 4e8$$, насколько помню).
Так что EPIC эффективен только там, где вы можете строго ограничить время одной даже самой длительной команды. Видимо, из подобных соображений Intel исключил из IA-64 простое целочисленное деление — он знал, что это долго, но "не знал" того же про чтение DRAM. Если у вас SRAM (умножьте цену памяти на 10-20) — ok, вперёд. Если у вас DSP (тоже, считаем, SRAM) — тоже пойдёт, туда подобные архитектуры внедрились и устоялись. Но для "обычного" современного компа, для толстого сервера — ой, зась.
И вслед этому очевидный вопрос про "импортозаместительный" Эльбрус-4. На синтетических тестах он много чего показывает, но про реальные задачи идёт много подпольных отзывов про жуткие тормоза...
N>>Не-а. Он и за прошедшие 20 лет не решил эти проблемы, и не решил бы их и тогда.
N>>Проблемы Itanium в том, что EPIC не работает с современной памятью и кэшами в мультипроцессорной системе.
G>А можно больше деталей?
Можно.
Пусть у вас последовательность команд. Какая-то часть из них кодирует операции только с регистрами — считаем, они доступны всегда, и на чтение и на запись. Какая-то обращается к памяти. Вот тут начинаются проблемы. Если у вас обыкновенная DRAM, у неё самая длительная операция это закрыть неактуальную строку (переписать из её кэша, который в самой микросхеме DRAM, в собственно DRAM-часть) и открыть заказанную (прочитать и переписать в тот кэш на борту DRAM). Эта операция занимает, согласно вики на DDR4, минимум 37.5 нс. (DDR3 и выше — в таймингах типа 10-10-10 сложить все 3 числа и подсчитать в тактах в clock rate (не путать с data rate).) В более бюджетных — и выше. Умножьте это на частоту процессора в гигагерцах и получите количество его тактов, сколько он ждёт. А ещё надо добавить время на опознание, что данных нет ни в одном локальном кэше (обычно добавляется где-то до 30 тактов), нет ни у одного партнёра по SMP (может быть и ~50 тактов) и на чтение полной строки (64 байта на x86) из DRAM (ну, ещё десятка два тактов, там высокая параллельность) — спокойно можно добрать и до 300 тактов.
Это чрезвычайно высокая цифра, даже если сравнивать с супердорогими арифметическими операциями типа целочисленного деления (128/64 на x86, считается, до 90 тактов).
Теперь представим себе исполнение этих команд команд. На x86 вполне может быть, что пока 30-я по счёту только-только прочиталась из памяти, 25-я раскодирована, 20-й назначили все внутренние регистры и она ждёт выполнения, 15-я выполняется, 10-я закончила и результаты готовы, но ждёт, пока предыдущие завершат операции, результат 7-й сидит во write queue на выходе в кэш, для 5-й заканчивают синхронизацию между кэшами в SMP, 1-я наконец всё типа закончила (слила результат в кэш в строку, которая эксклюзивно принадлежит на запись текущему ядру). В это время 2-я задумчиво занимается делением, и её результаты будут ещё тактов через 40. Но она (2-я) пишет в регистр, поэтому не тормозит заметную остальных; её результаты будут применены только к 15-й команде, а результаты той — только к 24-й, поэтому с остальными можно работать. Я не преувеличиваю в цифрах — у Skylake, например, цепочка от "наконец заканчиваем, фиксируем результаты" до "выбрали, начинаем декодировать" может быть до 224 микроопераций.
Процессор сам вычисляет, какая команда от какой зависит. Есть очевидные зависимости по регистрам (типа, если 3-я записала в eax, а 5-я его читает, 5-я должна выполняться после 3-й). Есть зависимости по памяти (x86 настаивает на том, что все операции записи в память упорядочены по этим действиям записи — хотя вычисления в них не требуют такой зависимости). Получается такой себе DAG (направленный ациклический граф) исполнений, внутри которого заметная свобода.
А теперь чем отличается EPIC? Само название поясняет: Explicitly parallel instruction computing. Параллельность рассчитана на этапе написания машинного кода (обычно — компилятором). Причём не в терминах "данная команда хочет результаты той, что на 15 раньше по цепочке" — такое бы требовало слишком много места для записи — а в виде группировок типа "данные команды друг на друга не влияют" (см. ниже), "можно параллелить сколько угодно по вкусу" и "а вот тут мы знаем явную зависимость, надо завершить все предыдущие до всех последующих". В доке по Itanium это выглядит так:
>> An instruction group is a sequence of instructions starting at a given bundle address and slot number and including all instructions at sequentially increasing slot numbers and bundle addresses up to the first stop, taken branch, Break Instruction fault due to a break.b, or Illegal Operation fault due to a Reserved or Reserved if PR[qp] is one encoding in the B-type opcode space. For the instructions in an instruction group to have well-defined behavior, they must meet the ordering and dependency requirements described below.
и вот главные слова — "must meet the ordering and dependency requirements". Автору машкода надо явно определить группы, в которых взаимовлияние минимизировано, и зафиксировать их. Дальше в доке много страшных слов, но вот одни из ключевых:
>> Between instruction groups, every instruction in a given instruction group will behave as though its read occurred after the update of all the instructions from the previous instruction group.
Процессор не имеет права посчитать, что какая-то команда из IG1 может быть выполнена одновременно с какой-то последующей IG2, если между ними стоит явный stop. Даже прочесть данные из памяти в регистр — потому что это называется update of architectural state. Память тормозит? Жди.
>> Within an instruction group, every instruction will behave as though its read of the register state occurred before the update of the register state by any instruction (prior or later) in that instruction group, except as noted in the Register dependencies and Memory dependencies described below.
[...]
>> Register dependencies: Within an instruction group, read-after-write (RAW) and write-after-write (WAW) register dependencies are not allowed (except пара незначительных исключений)
А это, наоборот, в одной группе не может быть зависимостей (как уже сказал — надо отбирать только независимые действия). Хотел исхитриться и применить результаты чтения сразу же? Обломись, бабка, мы на корабле.
Дальше в доке много чего — 100500 поправок, уточнений и исключений, словно специально, чтобы запутать всех и усложнить компилятор до предела. Но смысл основной тот же: пока на тех архитектурах, где процессор вычисляет зависимости на ходу (как x86), одна команда не тормозит соседних "товарок", пока её результаты не нужны, и может быть выделена из основного потока — на IA-64 такой свободы нет, учись предсказывать задержки по памяти — то, чего в принципе предсказать невозможно на обычном современном железе (если вы не занимаетесь Meltdownʼом).
Резонный вопрос — а почему вообще Intel решил, что возможно такую архитектуру сделать эффективной? А вот тут надо заглянуть в историю и заметить, что тогда же его топ-менеджмент попался на две удочки одновременно — первая под названием Rambus, а вторая — NetBurst (Pentium 4 должен был по тем планам стать последним x86). Супербыстрая DRAM плюс ориентация на потоковые SIMD действия (для "мультимедиа") и пренебрежение всеми остальными классами задач. Чем тут поможет SIMD? А именно тем, что для основных задач хорошо предсказывается необходимость подчитать память — можно заранее (на одну-две IG) сказать prefetch на нужный кусок, пока выполняются предыдущие, оно уже в L1. Но тут наступил облом — не стала мультимедия единственным использованием компьютера, а Rambus мало того, что выпустила память с бо́льшими задержками, так и оказалось, что все новомодные усовершенствования это фикция, а заодно патентный буллинг (Intel потеряла ок. 4e8$$, насколько помню).
Так что EPIC эффективен только там, где вы можете строго ограничить время одной даже самой длительной команды. Видимо, из подобных соображений Intel исключил из IA-64 простое целочисленное деление — он знал, что это долго, но "не знал" того же про чтение DRAM. Если у вас SRAM (умножьте цену памяти на 10-20) — ok, вперёд. Если у вас DSP (тоже, считаем, SRAM) — тоже пойдёт, туда подобные архитектуры внедрились и устоялись. Но для "обычного" современного компа, для толстого сервера — ой, зась.
И вслед этому очевидный вопрос про "импортозаместительный" Эльбрус-4. На синтетических тестах он много чего показывает, но про реальные задачи идёт много подпольных отзывов про жуткие тормоза...