[image]

Развитие аккумуляторной архитектуры процессоров

 
SE Татарин #07.04.2025 15:10
+
-
edit
 

Татарин

координатор
★★★★★
В идущем процессе написания "небайтного" процессора наткнулся на одну мысль, КМК, вполне годную и сейчас для каких-нить "нормальных" процессоров и контроллеров без всякой привязки к размерам слова.
Простая идея даёт некоторые ощутимые выгоды по сравнению с RISC (в каком-то смысле, это развитие RISC) и упрощает написание очень даже годного суперскалярного процессора до уровня, когда это посильно одиночке-любителю. А заодно значительно ускоряет выполнение, снижает нагрузку на регистры и повышает плотность кода. Одновременно. И бесплатно. :)
Предлагаю попинать ногами и показать на недостатки.

Начну чуть издалека, чтобы было понятно, почему и нафига.

Аксиоматично, любую команду программы можно исполнить только исполнив все команды, от которых она зависит. Этакий направленый граф, скорость продвижения по которому нужно максимизировать (для универсальных процессоров это возможно за счёт максимального числа параллельно выполняемых команд). Вопрос лишь в том, как оптимально представить этот граф в коде. Сори, но придётся прожевать общеизвестное.

Традиционно "стыковку" вершин рёбрами во всех популярных архитектурах делают через регистры. Например:
0001 add A, B, C
0002 add B, D, D
0005 sub C, E, F
Неважно, сколько команд находится между ними, 3-я команда зависит от первой, и это выражено через регистр R3: первая команда туда пишет, третья читает.

В традиционном RISC-суперскаляре это используется для внеочередного исполнения команд: если вторая команда не использует как аргумент результат первой, то её можно выполнять вне очереди.
Но это реально дорого - нужно в аппаратуре отслеживать эти зависимости, приходится переименовывать (внтури, аппаратурой) регистры. Но по сути хранение результата в регистре С нужно только для того чтобы связать команды 1 и 3 и передать результат из первой команды в третью. Только для этого. По сути - это не хранилище, это просто метка связи команд "результат отсюда нужно использовать здесь, здесь, а ещё здесь".
То есть, у нас написано, что "загрузить А, прибавить B, записать в С, потом загрузить С, вычесть Е, записать в F" а по факту нам нужно "загрузить А, прибавить B, вычесть Е, записать в F" (возможно, с записью и промежуточного результата, но это чуть позже). То есть, железо реально исполняет (тратит время, энергию) операции, которые нужны только для логической связи команд.

...
Теперь смотрим на безнадёжно устаревшую аккумуляторную архитектуру.
Фактически, аккумулятор там - параметр неявный, он почти всегда подразумевается, и часто используется как результат. Получается длинная сопля связанных команд, которые сложно разделить. Всем нужен аккумулятор, внеочередное исполнение очень сложно и редко когда в таком коде можно вычислить, малый размер единичной команды даёт всё равно низкую плотность кода из-за постоянной пересылки данных из и в аккумулятор... и т.д. и т.п.

...
А теперь допустим, что у нас код состоит из фрагментов, каждый из которых подразумевает загрузку чего-то, какую-то операцию и передачу результата дальше.
Допустим, у нас есть 3-6 команд (которые читаются пачкой в одном 32-бит или 64-битном слове), которые зависят друг от друга и поэтому обязаны выполняться последовательно. Не просто потому, что они так записаны, а потому что по сути кода они зависят друг от друга.
Мы загружаем некий регистр, что-то прибавляем, кладём результат в другой регистр, что-то вычитаем, умножаем и т.п.

То есть, вместо закодированной в слове команды (RISC) A -> вычесть B -> A
у нас в слове закодирована цепочка (именно последовательная, зависимая цепочка!) команд A -> вычесть B -> прибавить D -> сохранить в Е -> вычесть F -> A
Формат отдельной команды при этом предельно простой, на зависть любому RISC:
либо [opcode] [R], если команда имеет 2 аргумента и 1 результат
либо [0000] [opcode], если команда имеет 0-1 аргумент и 0-1 результат.
А размер достаточно мал: в 10-битной команде помещается 31+32 = 63 опкода.
Декодер предельно прост.

...
Что получается?

- Во-первых, мы резко сокращаем работу железа по переупорядочиванию инструкций, у нас же явно указана зависимость коротких цепочек команд. Точнее, нам эта работа почти не нужна теперь, можно и без неё. Отдельные цепочки могут выполняться одновременно (просто ожидая друг друга, если возникают взаимозависимости). "Нанонити" на уровне выполнения команд.
- Во-вторых, на каждой команде мы экономим адрес регистра (если сравнивать с компактной записью add A, B; A<-A+B) или дважды адрес регистра (если сравнивать с add A, B, C).
- В-третьих, мы резко экономим обращения к пулу регистров. Мы зачитали в одном слове на исполнение 6 команд, которые должны исполняться последовательно, мы можем коммутировать ИУ непосредственно друг на друга (или на буфера внутри, неважно), для передачи данных из команды в команду нам не нужно обращаться к регистрам и мешать другим, мы делаем это внутри блока. К регистрам мы обращаемся только когда нам реально нужно что-то взять из регистра или положить в регистр. Реально, резко! Для типичной команды - втрое!
- В-четвёртых, мы чуток экономим сами регистры (почти не нужны временные "разменные" регистры; в основном только регистры-переменные, в которых реально хранится что-то более осмысленное).
- В-пятых, простой и прямой декодер (команды почти-равны микрокоду) позволяет исполнять несколько последовательных простых команд за такт, просто коммутируя ИУ друг на друга. Доступа к внешним шинам не нужно же, всё внутри. Это критичное ускорение, сравнимое с тем, что делают очень сложными способами монструозные процессоры.
- В-пятых, и самое интересное для меня: резко упрощается реализация суперскалярности. Поскольку каждое слово состоит из просто (напрямую логикой за один такт) декодируемых команд, нужно только пометить регистры, которые будут изменены после прохождения блока, после изменения этого регистра - снимаем блок. Как только в качестве аргумента команды видим хоть один блокированный регистр, просто ждём. Как только деблокируется - едем дальше.
Каждое новое слово мы начинаем исполнять сразу, как только видим отсутствие блокированых операндов (или - об этом чуть позже - он не содержит метку зависимости).
Команды у нас уже упорядочены компилятором по зависимости, так что у нас почти никогда не возникает ситуации "мы можем что-то исполнить вне очереди". У нас ВСЁ по очереди.
- В-шестых, есть ещё выгоды по главной беде суперскаляров - переходам и долгому заполнению конвееров при неправильном предсказании, есть ещё в-седьмых и далее... но это обсуждаемо после.

Главные недостатки аккумуляторной архитектуры - невозможность одновременного исполнения команд завязаных на аккумулятор и мучения с пересылкой данных из и в аккумулятор. Но тут их нет. Первого - потому что у каждой короткой(!) цепочки команд аккумулятор свой. Второго нет по той же причине: каждая цепочка берёт регистр, что-то там внутри короткое-взаимозависимое делает и отдаёт регистр, зависимых команд в коде много, сформировать из них короткие цепочки можно всегда.
И даже если не получится найти достаточно длинную цепочку зависимых команд (ха! где такой код?), повышенная в 1.5-2 раза плотность кода позволяет даже ставить каждую вторую-третью команду пустой (но я не думаю, что это реальный случай; просто не представляю такой реальный код).

...
Но главное: система в реализации ПРОЩЕ типичного суперскаляра, требует меньше железа в реализации и (в частных случаях) быстрее.
   135.0.0.0135.0.0.0
Это сообщение редактировалось 07.04.2025 в 15:26
SE Татарин #07.04.2025 15:16
+
-
edit
 

Татарин

координатор
★★★★★
Подумалось, что это в некотором смысле VLIW, повёрнутый на 90 градусов: вместо записи в одном слове команд, которые должны исполняться параллельно, я явно указываю в одном слове команды, которые должны выполняться последовательно. Что всё-таки гораздо, прямо-таки ГОРАЗДО проще. :)

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

Ну и радикальная разница по сравнению с VLIW: не нужно монструозного компилятора, который в момент компиляции просчитывает всё на 100 ходов вперёд. Компилятор - самый обычный RISC, цепочки зависимых команд вычисляются в любом случае для генерации самого обычного кода, просто ставь их в одно слово пачкой, только и всего (для обычного суперскаляра раскидывать команды, чтобы их суперскаляру было удобно поднимать и исполнять даже сложнее).
Ну и неоптимальный код ничем таким особым не грозит, всё то же самое, что и при обычной оптимизирующей компиляции обычного кода обычного RISC-процессора - можно сделать код хуже, можно лучше, в целом даже попроще, пожалуй.
   135.0.0.0135.0.0.0
Это сообщение редактировалось 07.04.2025 в 16:40
RU Sandro #07.04.2025 16:32  @Татарин#07.04.2025 15:10
+
-
edit
 

Sandro
AXT

инженер вольнодумец
★★
Татарин> - В-третьих, мы резко экономим обращения к пулу регистров. Мы зачитали в одном слове на исполнение 6 команд, которые должны исполняться последовательно, мы можем коммутировать ИУ непосредственно друг на друга (или на буфера внутри, неважно), для передачи данных из команды в команду нам не нужно обращаться к регистрам и мешать другим, мы делаем это внутри блока. К регистрам мы обращаемся только когда нам реально нужно что-то взять из регистра или положить в регистр. Реально, резко! Для типичной команды - втрое!

Ты вообще на этом ничего не сэкономил. В конвейерных процессорах переиспользование результата предыдущей команды выполняется через result feedback, регистровый файл тут ни при чём.
Более того, регистровый файл многопортовый, поэтому одновременно читаются все аргументы, и по тактам — это бесплатно.
Да, есть известное исключение в виде Pentium Pro/II/III (и кошмар в виде Pentium 4). В результате проигрыша классической архитектуре от AMD там пришлось переделывать в нормальный вид.

Второе, время обращения к регистровому файлу и время выполнения операции в целочисленном АЛУ примерно одинаково. Каскадируя АЛУ, ты просто увеличиваешь длину такта и, соотвественно, создаёшь задержку, во время которой остальные блоки простаивают.

Третье. В реальном прикладном коде каждая шестая команда — это условный переход (± естественно). Это уменьшает возможное пакетирование.

...

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

Итаник утоп, если что. В нём так и ыло сделано.

Татарин> которые будут изменены после прохождения блока, после изменения этого регистра - снимаем блок. Как только в качестве аргумента команды видим хоть один блокированный регистр, просто ждём. Как только деблокируется - едем дальше.

Scoreboard. Карта регистров, которые ещё не содержат результат. Давно применяется, сам делал.

Татарин> Команды у нас уже упорядочены компилятором по зависимости, так что у нас почти никогда не возникает ситуации "мы можем что-то исполнить вне очереди". У нас ВСЁ по очереди.

Transmeta Crusoe. Не прокатило (хотя, объективно, шанс был).

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

Pentium 4. Не прокатило.

Татарин> Но главное: система в реализации ПРОЩЕ типичного суперскаляра, требует меньше железа в реализации и (в частных случаях) быстрее.

Примеры архитектур с реализацией этих предложений — выше. Оказалось, что "гладко было на бумаге, но забыли про овраги". Там в реальном окружении вылазит огромное количество граблей, в первую очередь — большая и непредсказуемая задержка от SDRAM. Поэтому на коне выехала AMD с простешим подходом: "против лома нет приёма". То есть, зафигачить настолько паралелльную машину, что её борзость компенсирует все периоды простоя из-за неготовности данных. Выяснилось, что это правлильный подход.

PS: Если что, как-то довелось делать конвейерную 32-битку со Scoreboard и полуасинхронным доступом к памяти. Дошло до живого кремния по 180нм.
   131.0131.0
SE Татарин #07.04.2025 17:10  @Sandro#07.04.2025 16:32
+
-
edit
 

Татарин

координатор
★★★★★
Татарин>> - В-третьих, мы резко экономим обращения к пулу регистров. Мы зачитали в одном слове на исполнение 6 команд, которые должны исполняться последовательно, мы можем коммутировать ИУ непосредственно друг на друга (или на буфера внутри, неважно), для передачи данных из команды в команду нам не нужно обращаться к регистрам и мешать другим, мы делаем это внутри блока. К регистрам мы обращаемся только когда нам реально нужно что-то взять из регистра или положить в регистр. Реально, резко! Для типичной команды - втрое!
Sandro> Ты вообще на этом ничего не сэкономил. В конвейерных процессорах переиспользование результата предыдущей команды выполняется через result feedback, регистровый файл тут ни при чём.
Да, я знаю (не то, как это реализовано в деталях, но сам факт).
Разница в том, что тут тот же самый результат реализован самым примитивным образом. Переиспользование результата в традиционной системе команд нужно ещё понять (связать регистры), а потом перенаправить куда надо. Тут ты просто согласно коду команды коммутируешь входы-выходы ИУ (можно напрямую) - и оно работает. Вот просто в лоб!

Sandro> Более того, регистровый файл многопортовый, поэтому одновременно читаются все аргументы, и по тактам — это бесплатно.
Не-не. Многопортовость регистрового файла - это очень не бесплатно, это я уже очень прочувствовал, когда начал писать VHDL для своего проца. По тактам результат как бы и тот, а вот "на реальные деньги гораздо дороже".
Поэтому каждый раз, когда ты можешь этого избежать - это хороший раз, а тут таких разов много, и это почти-бесплатно по железу, компилятор уже собрал команды в кучу, только пользуй. Например, в Эльбрусе десятипортовая(!) регистровая память. Мне достаточно число портов в (половина числа блочных исполнителей)..(число блочных исполнителей).

Sandro> Второе, время обращения к регистровому файлу и время выполнения операции в целочисленном АЛУ примерно одинаково. Каскадируя АЛУ, ты просто увеличиваешь длину такта и, соотвественно, создаёшь задержку, во время которой остальные блоки простаивают.
Тут не совсем тебя понял. Понятно, что блоки последующих команд простаивают - они ж последующие. Непонятно, почему это недостаток в сравнении с классическим RISC, где команды для выполнения требуют И пересылки, И (тактованного) времени на исполнение.
Пусть мы получили данные из регистров в буфера блоков, соединили в цепочку несколько АЛУ, запустили исполнение, аргументы по АЛУ побежали, АЛУ заработали по каким-то внутренним тактам... но это же всё внутри блока, не привязано к внешним тактам. Успеешь уложить 4 блока в два такта - хорошо. Не успеешь, получится в три? тоже неплохо! В обычном-то варианте с конвеером у тебя всё равно такт на пересылку, один такт на исполнение... а если пересылок три? Ну или у тебя идёт размен тактов на число портов в регистровой памяти, копии регистров и вот всё это... сложное. Тоже можно, но это уже другой порядок сложности, другие транзисторные бюджеты и другая сложность микросхемы (сложность техпроцесса тут тоже всплывает потом).

Sandro> Третье. В реальном прикладном коде каждая шестая команда — это условный переход (± естественно). Это уменьшает возможное пакетирование.
Да.
Но почему это плохо? Собссно, размер пакета и нужно выбирать достаточно маленьким, иначе будешь иметь дело со всеми проблемами классической аккумуляторной архитектуры, а преимущества по плотности кода уйдут (а где-то может стать и хуже).
Да, пакеты должны быть достаточно маленькими. Из условия "уложиться в слово" их особо большими сделать и нельзя, можно только предусмотреть для слова "метку зависимости", которая ставит данное слово с пакетом команд в "продолжение" текущему пакету, в очередь на тот же блок.

Что касается переходов, при данном подходе есть "естественное" решение проблемы штрафа за неправильное предсказание: условный игнор остатка пакета команд. Это почти как предикатное выполнение, но без недостатков предикатного подхода.

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

Татарин>> которые будут изменены после прохождения блока, после изменения этого регистра - снимаем блок. Как только в качестве аргумента команды видим хоть один блокированный регистр, просто ждём. Как только деблокируется - едем дальше.
Sandro> Scoreboard. Карта регистров, которые ещё не содержат результат. Давно применяется, сам делал.
Не знал, как называется.

Главное тут то, что это относительно простая штука, которую могу написать даже я. И её одной при такой организации кода достаточно для вполне себе эффективного суперскаляра!

Татарин>> Команды у нас уже упорядочены компилятором по зависимости, так что у нас почти никогда не возникает ситуации "мы можем что-то исполнить вне очереди". У нас ВСЁ по очереди.
Sandro> Transmeta Crusoe. Не прокатило (хотя, объективно, шанс был).
Почему не прокатило?
Но, НЯП, у Крузое другое было - всё же VLIW.

Sandro> Примеры архитектур с реализацией этих предложений — выше. Оказалось, что "гладко было на бумаге, но забыли про овраги". Там в реальном окружении вылазит огромное количество граблей, в первую очередь — большая и непредсказуемая задержка от SDRAM.
Не-не. Во всём, что касается памяти, нужен классический load-store.
Я вообще только про организацию кода тут. Прямо записывать зависимости команд - очень выгодно, можно повыкидывать кучу сложного и долгого железа с почти той же (или бОльшей) эффективностью выполнения. Но без заморочек VLIW.

Например, в 1.5 раза выше плотность кода - сама по себе большой бонус, в нынешних условиях затыка доступа к памяти это почти пропорциональный рост скорости выполнения на большом и сложном коде.
   135.0.0.0135.0.0.0
Это сообщение редактировалось 07.04.2025 в 17:16
RU Sandro #07.04.2025 19:09  @Татарин#07.04.2025 17:10
+
-
edit
 

Sandro
AXT

инженер вольнодумец
★★
Очень большая тема, тут надо детально отвечать, но по ключевому моменту отвечу сразу:

Татарин> В обычном-то варианте с конвеером у тебя всё равно такт на пересылку, один такт на исполнение... а если пересылок три?

В обычном конвейере с result feedback стоимость пересылки результата равна нулю. Им немедленно может воспользоваться следующая команда, в следующем же такте. (Не будем упоминать ту ересь, которую соорудили интели в P4)

Разметку наличия обратной связи делает декодер команд. Используя номер регистра, как индекс канала обратной связи. Это очень просто и дёшево аппаратно.
Соответственно, декодированная команда уже содержнит в нужном поле "читать не из регистрового файла, а из результата предыдущей операции".

То, что ты предлагаешь, уже и так автоматически работает.
   131.0131.0

pokos

аксакал

Татарин>> В обычном-то варианте с конвеером у тебя всё равно такт на пересылку, один такт на исполнение... а если пересылок три?
Sandro> В обычном конвейере с result feedback стоимость пересылки результата равна нулю.
Я бы ещё пояснил, что аккумулятор требует дополнительного такта синхронизации для защёлкивания в нём результата. А с шины можно брать данные сразу в потребителя, без дополнительного такта. Именно поэтому сейчас, когда экономить каждый ключ не нужно, аккумулятор не только не имеет никаких преимуществ, но и вреден.

Единственное живое применение для аккумулятора сегодня - это операции типа МАС - умножения-суммирование с накоплением. Но, нужда в их массированном применении случается не так часто. А на такой случай есть специально испечённые DSP. Или "нейронные" чипы.
   135.0.0.0135.0.0.0
SE Татарин #08.04.2025 10:18  @pokos#08.04.2025 09:49
+
-
edit
 

Татарин

координатор
★★★★★
Татарин>>> В обычном-то варианте с конвеером у тебя всё равно такт на пересылку, один такт на исполнение... а если пересылок три?
Sandro>> В обычном конвейере с result feedback стоимость пересылки результата равна нулю.
pokos> Я бы ещё пояснил, что аккумулятор требует дополнительного такта синхронизации для защёлкивания в нём результата. А с шины можно брать данные сразу в потребителя
Не, "аккумулятор" тут присутствует только в смысле организации кода. Типа, компактные команды устроены так, что они работают с предыдущим результатом, аккумулятор - метафора этого. Всё это как раз для того, чтобы работать, допустим, прямо с выхода ИУ, без лишних буферов-защёлок.

ld A, 10
sum A, 15
- выглядят понятнее.

Сам-то по себе, понятно, он никому не нужен.
   135.0.0.0135.0.0.0
CN pokos #08.04.2025 10:54  @Татарин#08.04.2025 10:18
+
-
edit
 

pokos

аксакал

Татарин> Не, "аккумулятор" тут присутствует только в смысле организации кода.
Ты уж как-то определись, есть он у тебя или нет или их 500 штук.
Татарин> потому что у каждой короткой(!) цепочки команд аккумулятор свой.
???

То, что тебе Sandro написал - это и есть твои виртуальные аккумуляторы, только не все. Нонешний процессор не только между этапами конвейера умеет, но и между параллельными ветками конвейеров.
Всё уже придумано и давно работает на уровне микрокода.
Посему, твой пример, имеющий зависимости по данным ни в каких отдельных аккумуляторах не хранится, а хранится в теневых копиях регистров, ожидая семафора. В это время нормальные регистры используются для других операций, да по нескольку раз!
А если зависимости по данным короче количества параллельных конвейеров, то всё ездит параллельно без промежуточного хранения вообще.
   135.0.0.0135.0.0.0
SE Татарин #08.04.2025 11:12  @pokos#08.04.2025 10:54
+
-
edit
 

Татарин

координатор
★★★★★
Татарин>> Не, "аккумулятор" тут присутствует только в смысле организации кода.
pokos> Ты уж как-то определись, есть он у тебя или нет или их 500 штук.
Татарин>> потому что у каждой короткой(!) цепочки команд аккумулятор свой.
pokos> ???
Метафорических - по штуке на каждый блок команд.

pokos> То, что тебе Sandro написал - это и есть твои виртуальные аккумуляторы, только не все.
То, что мне Сандро написал - это аналог, но виртуальные не аккумуляторы, а широкий сет регистров. Что отражается, например, на размерах кода.
add A, B, A
add A, D, A
add A, F, A
имеют на шесть кодов регистров больше, которые нужно ещё сопоставить друг с другом (на конвеере).

Ну и тот же функционал в командах
add A, B
add A, D
add A, F
декодируется за один так и команда представляет собой почти-микрокод, который (с небольшими поправками логикой) может прямо подаваться на коммутатор.

То есть - да, всё то же самое, в теории, уже реализуется в современных процах. Но! Декодирование и работа требует значительного конвеера и дополнительного железа. Те же теневые регистры и механизм переименования - не халява. Не уверен, что я смог бы повторить это дома в одиночку. А при коде как выше тот же самый функционал укладывается в абсолютно минимальное количество гейтов.

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

И вот это - уже не только проще современной реализации, но и лучше! Потому что очень короткий конвеер и простой декодер.

pokos> Всё уже придумано и давно работает на уровне микрокода.
Именно!
А тут - без микрокода, элементарная "компактная" команда в слове уже сама по себе микрокод, готовый к исполнению.
   135.0.0.0135.0.0.0
SE Татарин #08.04.2025 16:41
+
-
edit
 

Татарин

координатор
★★★★★
Ещё одна штука, оффтопично, показавшаяся мне удивительно простой, но отсутствующей в современных архитектурах (не в таком виде): бит подсказки при условном переходе, проставляемый компилятором.

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

Таких переходов в современном коде очень много - всякие проверки, обработка неверных параметров, исключений и т.п. Они содержат код, который в 99.(9)% случаях будет нормально пропущен. Но проверка должна быть сделана, а условный переход обработан, а за неверное предсказание - большой штраф. На уровне процессора шансов в первый раз угадать или не угадать, что этот "если" проставлен больше для перестраховки - один к одному, он же не знает, что тут проверяется входной параметр на нуль.
Но вот случаев, когда компилятор не смог бы (правильно) проставить этот бит - очень мало.

И один бит инструкции (или в какой-то иной форме) кажется небольшой платой за эффективное заполнение конвеера на множестве переходов. И даже если в какой-то раз с этим битом случается ошибка, ну, что же - имеем наш сегодняшний вариант, когда шансов угадать 50/50.
   135.0.0.0135.0.0.0
Это сообщение редактировалось 08.04.2025 в 16:47
RU Sandro #08.04.2025 17:10  @Татарин#08.04.2025 16:41
+
-
edit
 

Sandro
AXT

инженер вольнодумец
★★
Татарин> Ещё одна штука, оффтопично, показавшаяся мне удивительно простой, но отсутствующей в современных архитектурах (не в таком виде): бит подсказки при условном переходе, проставляемый компилятором.

У кого-то совершенно точно было. Сходу не вспомню, но было.

В x86 есть полуофициальное использование префиксов ... эээ ... вроде 0x2E/0x3E, но неясно, какие нынешние камни это поддерживают, если поддерживают вообще. Если кто не поддерживает, то для него это префиксы-пустышки.

Добавлю: если условные переходы могут быть длинные, то компилятор может этого добиться простой реорганизацией кода. При статическом предсказании принято считать, что переход назад скорее всего будет выполнен (циклы же), а вперёд — нет. Поэтому достаточно сложить все обработчики сомнительных случаев после основного тела функции.

Хотя есть нехорошие люди, которые возвращают значение не через return, а через throw. Хуже того, есть написанные таким образом библиотеки и даже языки. Там цикл — это итератор, а завершение работы итератора делается через выбрасывание исключения.
   131.0131.0
Это сообщение редактировалось 08.04.2025 в 17:17
EE Татарин #08.04.2025 19:06  @Sandro#08.04.2025 17:10
+
-
edit
 

Татарин

координатор
★★★★★
Sandro> У кого-то совершенно точно было. Сходу не вспомню, но было.
Есть схожее, далеко ходить не нужно - у того же Эльбруса ты можешь сначала (сложным образом) заявить условие переход, а потом где-то там выполнить переход. Но это не совсем то.

Sandro> В x86 есть полуофициальное использование префиксов ... эээ ... вроде 0x2E/0x3E, но неясно, какие нынешние камни это поддерживают, если поддерживают вообще.
Интересно, в первый раз слышу.

Sandro>При статическом предсказании принято считать, что переход назад скорее всего будет выполнен (циклы же), а вперёд — нет.
Звучит очень логично. То есть, почти всегда исполняемый код мы предваряем инвертированным условием перехода вперёд, а редко исполняемый выносим далеко вперёд, по исполнении его вернёмся безусловным?
Ну да, тогда понятно, почему нет признака. :) Выглядит несложно по меркам нашего обычного цирка.

Sandro> Хотя есть нехорошие люди, которые возвращают значение не через return, а через throw.
Ну, знаешь, всегда можно "обмануть систему"© и написать код, который на железе работает плохо, медленно или через хак и через раз. Да, можно. Ну и? :)
Я бы сказал, что это уже проблемы нехороших людей.
   135.0.0.0135.0.0.0
Это сообщение редактировалось 09.04.2025 в 00:38
RU Дем #08.04.2025 21:26  @Татарин#07.04.2025 15:10
+
-
edit
 

Дем
Dem_anywhere

аксакал


Татарин> В идущем процессе написания "небайтного" процессора наткнулся на одну мысль
ИМХО не совсем в ту сторону, надо больше не на аккумулятор смотреть а на сумматор
Которому надо на вход скоммутировать два регистра, а выход направить в третий.
В теории, этот выход можно скоммутировать на другой сумматор как один из входящих... но это если бы процессор знал что результат потом не понадобится.
А так вдруг когда-нибудь после условного перехода этот регистр С используется как источник?

Ну тут разве что явно сделать, что регистры с номерами от и до - временные, существующие только до перехода.
   137.0137.0
EE Татарин #09.04.2025 00:16  @Дем#08.04.2025 21:26
+
-
edit
 

Татарин

координатор
★★★★★
Татарин>> В идущем процессе написания "небайтного" процессора наткнулся на одну мысль
Дем> ИМХО не совсем в ту сторону, надо больше не на аккумулятор смотреть а на сумматор
Зачем на него смотреть? Почему именно на сумматор?

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

Все результаты команд в слове направляются на вход следующей команде (если вообще ей нужен аргумент). В этом весь смысл. Но только в пределах слова.
   135.0.0.0135.0.0.0
EE Татарин #09.04.2025 00:32
+
-
edit
 

Татарин

координатор
★★★★★
Ну и пример возможных (я не определился, что лучше) форматов слова с компактными командами (вторую форму - 64бит-команду придётся оставить для особых случаев и для расширений), просто чтобы было понятно, почему я вообще говорю про плотность кода.

Предельно в 64 битах можно уместить до 7(!) 9-битных команд, каждая из которых имеет 0-2 входных операндов и 0-1 выходных, и в 9 бит при использовании аккумулятора как одного из аргументов и результата вполне помещается либо
- 4 бита кода команды + 5 бит регистра (32 регистра уже достаточно кучеряво, чтобы компилятору не было тесно в пределах типичного вызова)
- 9 бит кода команды.
Что даёт 15 команд с двумя параметрами cmd A,R и ещё 32 команды вида cmd A. Что закрывает всю арифметику + базовые команды.

7 команд на 64-битное слово - это почти вдвое лучше, чем компактные команды очень продуманых 32-битных RISC (16 бит команда, 4 в 64-бит слове). Или втрое+ лучше, чем обычные 32-битные команды RISC (2 штуки в 64-бит слове).

Но это, конечно, немножко уже край. Не факт, что такое количество взаимозависимых команд в слове уже не перебор.
   135.0.0.0135.0.0.0
CN pokos #09.04.2025 07:37  @Татарин#08.04.2025 11:12
+
-
edit
 

pokos

аксакал

Татарин> И вот это - уже не только проще современной реализации, но и лучше! Потому что очень короткий конвеер и простой декодер.
Ну, я ж тебе писал уже, что экономить кремний нынче не принято. Нынешняя задача - увеличение продаж.

Что касается простых решений, то за 50 лет все они уже придуманы. И если ты, вдруг, придумал что-то новое, посмотри внимательно, скорее всего, это всё уже где-то было. Но не продавалось, и поэтому забыто.
   135.0.0.0135.0.0.0
Это сообщение редактировалось 09.04.2025 в 08:58
SE Татарин #09.04.2025 10:02  @pokos#09.04.2025 07:37
+
+1
-
edit
 

Татарин

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

pokos> Что касается простых решений, то за 50 лет все они уже придуманы. И если ты, вдруг, придумал что-то новое, посмотри внимательно, скорее всего, это всё уже где-то было. Но не продавалось, и поэтому забыто.
Не уверен в том, потому что периодически вижу контрпримеры. Иногда люди бывают фантастически слепы.
Скорее всего - да, уже было. Но если после осмотра вокруг ты такого не видишь, равно как и не видишь принципиальных косяков в идее, есть шанс, что это что-то новое. Лишь шанс, да.
Но есть.

Вообще, довольно часто неудача технической идеи сопряжена с, казалось бы, совершенно левыми обстоятельствами - какой-то мелкой додумки-детали не хватило, кто-то дал деньги, кто-то не дал деньги, сменилась мода, идея была высказана не в то время или не в той стране, дочка изобретателя внезапно получила травму позвонка... такого полно. Ещё больше такого, что, вроде, всё правильно сделали, но другое техническое решение в том же изделии или реализация подкачала. Бывает, в общем.
   135.0.0.0135.0.0.0
Это сообщение редактировалось 09.04.2025 в 10:18
RU Дем #09.04.2025 11:45  @Татарин#09.04.2025 00:16
+
-
edit
 

Дем
Dem_anywhere

аксакал


Татарин> Зачем на него смотреть? Почему именно на сумматор?
Потому что сумматор процессору нужен, а аккумулятор как отдельная сущность - не факт.
Татарин> Все результаты команд в слове направляются на вход следующей команде (если вообще ей нужен аргумент). В этом весь смысл. Но только в пределах слова.
Если нам промежуточный результат не нужен - почему не сделать сумматор не на два входящих операнда, а на 4-8?
То же вычисление адреса в х86-64 - до трёх операндов и ещё с возможностью разрядного сдвига
   134.0134.0
CN pokos #09.04.2025 12:06  @Татарин#09.04.2025 10:02
+
-
edit
 

pokos

аксакал

Татарин> ... кто-то дал деньги, кто-то не дал деньги...
О, да! Тхат из тру!
   135.0.0.0135.0.0.0
SE Татарин #09.04.2025 14:54  @Дем#09.04.2025 11:45
+
-
edit
 

Татарин

координатор
★★★★★
Татарин>> Все результаты команд в слове направляются на вход следующей команде (если вообще ей нужен аргумент). В этом весь смысл. Но только в пределах слова.
Дем> Если нам промежуточный результат не нужен - почему не сделать сумматор не на два входящих операнда, а на 4-8?
Вот как раз потому, например, что
Дем> То же вычисление адреса в х86-64 - до трёх операндов и ещё с возможностью разрядного сдвига

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

Но если компилятор их (те же самые операции) сгруппирует и запишет друг за другом, то во-первых, можно повыкидывать 1/2..2/3 кодов операндов, а во-вторых, просто и дёшево обеспечить их последовательное выполнение, простой коммутацией ИУ друг на друга.
   135.0.0.0135.0.0.0

SEA

опытный

pokos> Ну, я ж тебе писал уже, что экономить кремний нынче не принято. Нынешняя задача - увеличение продаж.

Как правило, это ведёт к увеличению потребления и снижению доступной скорости. Если, конечно, этот лишний кремний не служит как раз для уменьшения потребления.

pokos> Что касается простых решений, то за 50 лет все они уже придуманы. И если ты, вдруг, придумал что-то новое, посмотри внимательно, скорее всего, это всё уже где-то было. Но не продавалось, и поэтому забыто.

Это абсолютно верно.
Хотя остались возможности выигрыша в случае комбинации аналогов старых решений.
   132.0.0.0132.0.0.0

pokos

аксакал

SEA> Как правило, это ведёт к увеличению потребления ...
Ничего страшного, если это увеличивает продажи.

SEA> ....и снижению доступной скорости.
С чего вдруг? То, о чем я писал, предназначено, как раз, для увеличения доступной скорости.
   135.0.0.0135.0.0.0

в начало страницы | новое
 
1961: День Космонавтики (2025 лет).
Поиск
Настройки






Статистика
Рейтинг@Mail.ru
АвиаТОП
 
Яндекс.Метрика
website counter
 
free counters