RISC-V — это архитектура набора команд (ISA) для микропроцессоров, которую либо любят, либо ненавидят. Наблюдается даже некоторое соперничество между лагерем ARM и RISC-V.

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

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

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

И у ARM, и у RISC-V есть как достоинства так и недостатки. Но в критике лучше оперировать фактами, а не заблуждениями. Относительно RISC-V существует довольно много заблуждений, и я хочу их развеять. Постараюсь охватить наиболее популярные из тех, с которыми я столкнулся.

Миф 1: RISC-V «раздувает» размер программ

Инструкция RISC-V в среднем делает меньше работы, чем ARM. В ARM есть инструкция LDR для загрузки данных из памяти в регистр. Она создана для выполнения типичного кода C/C++, например:

// Код C/C++
int a = xs[i];

Например, мы хотим загрузить данные из массива xs по индексу i. Можно перевести код в ARM, где регистр x1 содержит стартовый адрес массива xs, а регистр x2 — индекс i. Нам надо посчитать сдвиг в байтах от начального адреса x1. Для массива 32-bit целых это соответствует умножению индекса i на 4 для получения сдвига в байтах. Для ARM то же самое можно сделать двойным сдвигом регистра x2 влево.

x1 ← mem[x1 + x2<<2]

mem соответствует основной памяти. Мы используем x1, x2 и сдвиги 2, 4 или 8 для получения исходного адреса. Целиком инструкция будет выглядеть так:

; 64-битный код ARM
LDR x1, [x1, x2, lsl #2] ; x1 ← mem[x1 + x2<<2]

В RISC-V эквивалент потребует целых три инструкции (# отмечает комментарии):

# Код RISC-V
SLLI x2, x2, 2   # x2 ← x2 << 2
ADD  x1, x1, x2  # x1 ← x1 + x2
LW   x1, x1, 0   # x1 ← mem[x1 + 0]

Выглядит это как огромное преимущество ARM: код плотнее в 3 раза. Вы получаете меньшее использование кэша и большую производительность конвейера команд.

Сжатые инструкции спешат на помощь!

Однако RISC-V поддерживает сжатые инструкции добавлением всего 400 логических вентилей (AND, OR, NAND) к чипу. Таким образом, две инструкции (из наиболее частотных) могут поместиться в 32-битное слово. Круче всего, что сжатие не добавляет задержек — это не zip-декомпрессия. Декомпрессия выполняется в процессе обычного декодирования инструкций, поэтому она «мгновенная».

# Код RISC-V
C.SLLI x2, 2     # x2 ← x2 << 2
C.ADD  x1, x2    # x1 ← x1 + x2
C.LW   x1, x1, 0 # x1 ← mem[x1 + 0]

Сжатые инструкции следуют закону Парето:

20% инструкций используются 80% времени.

Не воспринимайте это буквально. Создатели RISC-V тщательно отобрали инструкции и сделали их частью сжатого набора команд.

Таким образом RISC-V легко превосходит ARM в плотности кода.


На самом деле современные ARM-чипы могут работать в двух режимах: либо в 32-битном режиме (AArch32) для обратной совместимости, либо в 64-битном режиме (AArch64). В 32-битном режиме чипы ARM поддерживают сжатые инструкции Thumb.

Уточнение: Не путайте 64-битный режим с длиной инструкции. Инструкции в 64-битном режиме по-прежнему имеют ширину 32 бита. Смысл 64-разрядного ARM заключается в возможности работать с 64-разрядными регистрами общего назначения. Для 64-разрядного режима ARM полностью переработал набор команд, поэтому нам надо четко понимать, о каком наборе команд мы говорим.

Такое различие менее важно для RISC-V, где 32-битный набор команд (RV32I) и 64-битный набор команд (RV64I) почти идентичны. Это связано с тем, что дизайнеры RISC-V думали о 32-разрядных, 64-разрядных и даже 128-разрядных архитектурах при разработке RISC-V ISA.


Вернемся к сжатым инструкциям RISC-V. Хотя меньшее потребление памяти инструкциями хорошо для кэша, это не решает всех проблем. У нас все еще больше инструкций для декодирования, выполнения и записи результатов. Однако это можно решить с помощью макро-оперативного слияния (macro-op fusion, МОС).

Уменьшаем количество инструкций макро-оп слиянием (МОС)

МОС превращает несколько инструкций в одну. Создатели RISC-V отмечают паттерны кода, которые компиляторы должны выдавать, чтобы помочь проектировщикам чипов RISC-V добиться МОС. Одно из требований состоит в том, чтобы у операций был один и тот же регистр назначения (место, куда сохраняется результат). В нашем примере обе инструкции ADD и LW сохраняют результат в регистр x1, мы можем соединить их в одну операцию в декодере инструкций.

Это правило позволяет избежать одного из возражений против МОС: поддержка сохранения результатов в нескольких регистрах в соединенных инструкциях превращается в головняк. Нет, это не потому, что каждая инструкция должна использовать один и тот же регистр назначения, чтобы она была соединена. Наоборот, по этой причине объединенную инструкцию не нужно записывать в несколько регистров.

Я уже писал более подробные статьи на эту тему:

Замечание о МОС

МОС — это не бесплатный обед. Вам нужно добавить больше транзисторов в декодеры для поддержки МОС. Вам также придется работать с разработчиками компиляторов, чтобы они генерировали шаблоны кода, которые создают шаблоны инструкций, которые можно слить. Разработчики RISC-V как раз занимаются этим в данный момент. Иногда МОС просто не сработает, например, из-за границ линии кэша (cache-line). Однако эти проблемы актуальны и для x86 с их инструкциями произвольной длины.

Миф 2: Инструкции переменной длины усложняют параллельное декодирование инструкций

В мире x86-инструкция в принципе может быть бесконечной длины, хотя для практических целей она ограничена 15 байтами. Это усложняет разработку суперскалярных процессоров, которые параллельно декодируют несколько команд. Почему? Потому что, когда вы получили, скажем, 32 байта кода, вы не знаете, где начинается каждая отдельная инструкция. Решение этой проблемы требует использования сложных методов, которые часто требуют больше циклов для декодирования.

Подробнее: Decoding x86: From P6 to Core 2 — Part 1

Есть мнение, что методы, используемые в Intel и AMD, основаны на грубом методе проб и ошибок, когда просто делаются многочисленные предположения о том, где инструкции начинаются и заканчиваются.

Однако дополнительная сложность сжатых инструкций для RISC-V тривиальна. Выбранные инструкции всегда будут выровнены по 16 бит. Это значит, что каждый 16-битный блок — это либо начало 16/32-битной инструкции, либо конец 32-битной инструкции.

Этот простой факт можно использовать для разработки различных способов параллельного декодирования инструкций RISC-V. Я уже не дизайнер чипов, но даже я могу придумать схему для достижения этого. Например, мог бы связать декодеры с каждым 16-битным блоком, как показано на диаграмме ниже.

Затем каждый декодер получит свой первый вход из связанного 16-битного блока. Таким образом, первая часть инструкции для декодера D2 будет взята из блока команд B2. А вторая часть — из B3, если у нас 32-битная инструкция.

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

Для сравнения, я понятия не имею, как бы я декодировал x86-инструкции, которые могут быть длиной от 1 до 15 байт. Выглядит это как значительно более сложная задачка.

В любом случае, создатели RISC-V говорят, что это не сложная проблема. То, что я описал, — попытка объяснить, почему это несложно, с точки зрения не профессионала. Только не зацикливайтесь на предложенном решении — очевидно, что ему не хватает деталей. Например, вам точно понадобится какая-то логика для переключения мультиплексоров.

Миф 3: Отсутствие условного исполнения было ошибкой

В 32-битном коде ARM (ARMv7 и более ранние) есть условные инструкции (conditional instructions). Берем обычную инструкцию вроде LDR (загрузка в регистр) или ADD и отслеживаем условие выполнения  EQ для равенства или NE для неравенства — получаем соответствующие условные инструкции LDREQ and ADDNE.

; 32-битный ARM код
CMP   r6, #42
LDREQ r3, #33 ; r3 ← 33, if r6 = 42
LDRNE r3, #12 ; r3 ← 12, if r6 ≠ 42

CMP   r3, #8
SUBS  r3, r6, #42
ADDEQ r3, #33     ; r3 ← r3 + 33, if r6 - 42 = 0
ADDNE r3, #12

Инструкция CMP устанавливает один из флагов, проверяемых при выполнении условных инструкций. Инструкции исполняются только в том случае, если условие выполнено. Если непонятно, предлагаю изучить дополнительные материалы:

  • ARM, x86 and RISC-V Microprocessors Compared — разбор уникальных особенностей каждого процессора, включая условное выполнение.

  • Conditional Execution — разбор условного выполнения на 32-разрядной архитектуре ARM (AArch32) Azeria Labs (отличные учебники ARM).

В прикрепленной статье я также объясняю, почему ребята из RISC-V отказались от условного исполнения. Она значительно затрудняет реализацию Out-of-Order Execution (OoOE), а это очень важно для создания высокопроизводительных чипов. На самом деле современные 64-разрядные процессоры ARM (ARMv8 и выше) не имеют условного исполнения по этой же причине. В них есть только инструкции по условному отбору (CSEL, CSINC), но они выполняются безусловно.

Подробнее: Conditional select instructions in ARM AArch64

С другой стороны, в лагере ARM отсутствие условных инструкций убивает производительность даже на простых микропроцессорах без OoOE (многие 32-разрядные ARM). Предполагается, что в процессоре с малым количеством транзисторов не реализовать достаточно сложный предсказатель ветвлений, способный избежать потери производительности из-за ошибок ветвлений (miss-prediction).

Но у ребят из RISC-V снова есть решение. Если вы посмотрите на ядра серии SiFive 7, они фактически реализовали МОС, чтобы справиться с этим. Короткие ветви из одной инструкции могут быть объединены в одну ARM-подобную условную инструкцию. Рассмотрим код:

# Код RISC-V
BEQ x2, x3, done  # перейти к done, если x2 == x3
ADD x4, x5, x6    # x4 ← x5 + x6
done:
SUB x1, x3, x2

Поскольку переход к done — это одна инструкция, чип серии SiFive 7 может распознать этот шаблон и объединить в одну инструкцию. В псевдокоде это будет:

# Код RISC-V
# если x2! = x3 затем x4 ← x5 + x6
ADDNE x2, x3, x4, x5, x6
SUB x1, x3, x2

Эта функция называется short-forward-branch-оптимизацией. Подчеркну, что инструкции ADDNE нет — это просто способ пояснить происходящее. Преимущество такого МОС в том, что мы избавляемся от ветвления. И значит, мы избавляемся от затрат на ошибки предсказания ветвления (вызывающее сброс конвейера инструкций). Вот что пишут SiFive в патентной заявке:

Например, условное ветвление через одну или несколько инструкций может быть объединено. В некоторых реализациях условное ветвление объединяется со следующей инструкцией. Таким образом, комбинация выполняется как одна инструкция без ветвления.

<...>

МОС может выполняться как инструкция без ветвления, что позволяет избежать сброса конвейера, а также избежать замусоривания состояний предсказателя ветвлений. Последовательность макроопераций для объединения может включать в себя множество инструкций, следующих за условной инструкцией (control-flow instruction).

В лагере ARM могут возразить, что полагаться на МОС плохо, потому что это добавляет сложности и транзисторов. Однако ARM обычно использует МОС для объединения CMP с инструкциями ветвления типа BEQ или BNE. В RISC-V условные переходы выполняются в одну инструкцию, и только около 15% кода — это инструкции ветвления.

Таким образом, RISC-V имеет большее преимущество. ARM может поспорить с этим преимуществом, используя МОС. Но раз macro-op fusion — честная игра для ARM, то RISC-V тоже может пользоваться этим инструментом.

Комментарий переводчика

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

Миф 4: RISC-V использует устаревшую векторную обработку вместо современных SIMD-инструкций

Некоторые люди в лагере ARM хотят создать впечатление, что дизайнеры RISC-V застряли в прошлом и не в курсе последних достижений микропроцессорной архитектуры. Дизайнеры RISC-V решили использовать векторную обработку вместо SIMD (Single Instruction Multiple Data). Ранее это было популярно в старых суперкомпьютерах Cray. Позднее SIMD были добавлены в x86-процессоры для мультимедийных приложений.

Может показаться, что SIMD — это более новая технология, но это не так. SIMD впервые появились на компьютере Lincoln TX-2, используемом для реализации первого графического интерфейса под названием Sketchpad, созданного Иваном Сазерлендом.

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

SIMD были добавлены в x86-процессоры довольно хаотично, без планирования, для получения небольшого повышения скорости в работе с мультимедиа.

SIMD проще, чем набор команд для настоящей векторной обработки. Однако с расширением возможностей SIMD и увеличениями их длины мы получили нечто более сложное, чем векторная обработка. Нечто неряшливое и негибкое.

Факт, что Cray использовал векторную обработку в 1980-х годах, не означает, что векторная обработка — устаревшая технология. Это все равно, что сказать, будто колесо — это устаревшая технология, потому что оно давненько появилось.

Векторная обработка стала актуальной, потому что от нее выигрывает машинное обучение, продвинутая графика и обработка изображений. Это области, в которых нужна производительность. И ребята из RISC-V не единственные, кто это понял. ARM добавила свои собственные инструкции векторной обработки — SVE2. Если бы они были устаревшими или ненужными, пожалуй, их не добавляли бы в процессоры.

Миф 5: Векторная обработка не нужна, просто используйте видеокарты

Еще один аргумент против векторной обработки, который мне встретился, состоит в том, что для более длинных векторов вообще не нужен CPU. Просто оставьте их современной видеокарте.

Однако в таком подходе есть несколько проблем:

  1. Не всегда RISC-V-код будет выполняться на высокопроизводительных рабочих станциях с мощными видеокартами.

  2. Передача данных между памятью CPU и графического процессора вызывает задержки.

  3. Видеокарты на самом деле не спроектированы для векторной обработки общего назначения, даже если они используются для этого и преуспевают в этом.

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

Передача данных для обработки на видеокарту и обратно добавляет накладные расходы и необходимость в планировании. Если у вас нет достаточного количества данных для отправки, то эффективнее обрабатывать данные локально на CPU.

Видеокарты изначально предназначались для обработки графических данных, таких как вершины и пиксели, с использованием шейдеров (шейдер — специализированная подпрограмма для выполнения на GPU — прим. переводчика). Постепенно видеокарты стали более универсальными, но наследие никуда не делось.

Недаром многие стартапы стремятся бросить вызов Nvidia на рынке ИИ, создавая специализированные карты-ускорители ИИ. Они видят возможность конкурировать в создании оборудования, предназначенного специально для ИИ, а не графических вычислений.

Например, Esperanto Technologies с чипом SOC-1. Создали решение на основе процессорных ядер RISC-V, конкурирующее с GPU Nvidia A100. Карта A100 была специально разработана для ускорения вычислений машинного обучения. При этом в A100 чуть более 100 ядер (многопроцессорных SM).

Esperanto Technologies реализовали 1092 ядра в чипе, который потребляет всего 20 Вт мощности. A100, для сравнения, потребляет 250-300 Вт.

Это позволяет собрать шесть чипов SOC-1 на одной плате и уложиться всего в 120 Вт. Обычный стоечный сервер потребляет около 250 Вт.

Таким образом, можно сделать плату с более чем 6000 ядрами RISC-V и уместить в обычный сервер для выполнения задач искусственного интеллекта.

Все это возможно благодаря маленьким и простым ядрам RISC-V с векторной обработкой. Esperanto использует эту стратегию вместо создания многопроцессорных ядер, как на видеокарте Nvidia.

Другими словами, идея «просто используйте видеокарту для векторной обработки» не работает. Хорошо продуманные RISC-V-ядра с векторной обработкой превзойдут видеокарту в векторной обработке и будут потреблять меньше энергии.

Откуда появился озвученный в подзаголовке миф? От людей, представляющих CPU только в виде больших жирных ядер в стиле x86. Добавление векторной обработки к нескольким большим x86 ядрам не имеет смысла. Но кто сказал, что вам нужно создавать большие жирные ядра? С RISC-V можно создавать ядра любого типа. SOC-1 имеет как жирные out-of-order суперскалярные ядра, так и крошечные in-order ядра c векторной обработкой.

Миф 6: Современный ISA должен справляться с целочисленным переполнением

Часто критикуется факт, что RISC-V не вызывает аппаратного исключения (hardware exception) и не устанавливает никаких флагов в случае переполнения при исполнении целочисленных арифметических инструкций. Кажется, почти в любой дискуссии о RISC-V в качестве аргумента используется тезис, что дизайнеры RISC-V застряли в 1980-х годах (тогда никто не проверял на целочисленные переполнения).

Тема сложная, так что начнем с простого. Большинство компиляторов для популярных языков не вызывают переполнения по умолчанию. Примеры:

  • C/C++ и Objective-C

  • RUST (не для релиза)

  • GO

  • Java/Kotlin

  • C# (используйте checked блок)

Некоторые из них не делают этого даже в режиме отладки по умолчанию. В Java для исключений переполнения надо вызвать addExact и subtractExact. В C# надо написать код в контексте checked:

try {
    checked {
        int y = 1000000000;
        short x = (short)y;
    }
}
catch (OverflowException ex) {
    MessageBox.Show("Overflow");
}

В Go надо использовать библиотеку overflow с функциями overflow.Add, overflow.Sub или варианты, вызывающие «панику» (аналог исключения), такие как overflow.Addp и overflow.Subp.

Единственный популярный компилируемый язык, вызывающий исключение при целочисленном переполнении, — это Swift. И если погуглить, обнаружится много недовольных этим людей. Так что утверждение о серьезности недостатка этой фичи в RISC-V кажется странным.

Многие языки с динамической типизацией, например Python, при переполнении изменяют размер целых чисел на более крупные типы (с 16 бит на 32, потом на 64 и так далее — прим. переводчика). Но эти языки настолько медленные, что дополнительные инструкции, необходимые на RISC-V, не имеют значения.

Миф 7: Обработка целочисленного переполнения раздута

Обычно при обсуждении целочисленного переполнения утверждается, что RISC-V требует в четыре раза больше кода, чем другие архитектуры. Однако это худший случай. Большинство случаев можно обработать, просто добавив инструкцию ветвления.

Проверка переполнения при сложении беззнаковых целых чисел:

add  t0, t1, t2        # t0 ← t1 + t2
bltu t0, t1, overflow  # перейти к overflow, если t0 < t1

Знаковые целые числа, когда известен знак одного аргумента:

addi t0, t1, 42        # t0 ← t1 + 42
blt  t0, t1, overflow  # перейти к overflow, если t0 < t1

Общий случай с двумя операндами, для которых знак неизвестен:

add  t0, t1, t2        # t0 ← t1 + t2
slti t3, t2, 0         # t3 ← t2 < 0
slt  t4, t0, t1        # t4 ← t0 < t1
bne  t3, t4, overflow  # перейти к overflow, если t3 ≠ t4
Примечание переводчика

Тут не осилил проверку кода в уме. Если нашли ошибку или что-то непонятно, напишите мне.

Миф 8: Целочисленное переполнение дешево в реализации

Получение и хранение бита переполнения недорого только в сферическом вакууме. Это все равно что сказать, будто функция мутации в программировании недорогая с точки зрения памяти или циклов процессора. Но сейчас не об этом.

Один из аргументов, за который топят адвокаты чистых функций (non-mutating) в программировании, в том, что их легче выполнять параллельно, чем мутирующие c общим состоянием. То же самое относится и к out-of-order суперскалярным процессорам. Суперскалярные процессоры выполняют множество команд параллельно. Выполнение инструкций параллельно проще, если инструкции не имеют общего состояния.

Регистры статуса вводят общее состояние и, следовательно, зависимости между инструкциями. Вот почему RISC-V был разработан так, чтобы не иметь никаких регистров статуса. Инструкции ветвления в RISC-V, например, сравнивают два регистра напрямую, а не читают регистр статуса.

Для поддержки регистров статуса в суперскалярном процессоре, где инструкции выполняются не по порядку, вам нужен более сложный учет. Речь идет не просто о добавлении пары триггеров для хранения бита.

Итог

Большая часть критики RISC-V основана на непонимании технологий и отсутствии общей картины. То, как RISC-V позволяет создавать гетерогенную вычислительную среду с ядрами, оптимизированными для различных типов вычислений, работающих вместе, непривычно для людей.

Так или иначе, это не новая идея. Playstation 3 имел аналогичную архитектуру под названием Cell с ядром общего назначения Power Processor Element (PPE) и векторными процессорами специального назначения Synergistic Processing Elements (SPE).

Однако PPE и SPE имели разные наборы команд, что осложняло работу с архитектурой Cell. У современных систем RISC-V преимущество в одинаковом базовом наборе команд и регистров для ядер всех типов.

В блоге Selectel  мы не первый раз пишем про эту архитектуру. Вам может быть интересно:

Что означает RISC и CISC?

Оценка RISC-ов: когда ожидать серверы на ARM в дата-центрах

Esperanto: производительный 1000-ядерный RISC-V процессор для систем машинного обучения

Комментарии (27)


  1. permeakra
    27.04.2022 20:22

    к 6)

    Сигнальный флаг удобно иметь, если вам нужно много считать. В этом случае можно прогнать длиииииннный расчет, а потом проверить установленные флаги караула. ЕМНИП, такой юзкейз предусмотрен для плавучки в Фортране и новом Си.


    1. hard_sign
      28.04.2022 10:13

      А разве флаги не сбрасываются после каждой операции?


      1. permeakra
        28.04.2022 11:47
        +1

        FPUшные по-моему нет, для этого отдельная команда есть FCLEX/FNCLEX . Ну и к этому самому доступ есть из языка, функция ieee_flags в фортране и концепкция Floating-point environment в C99 и вверх.


  1. redsh0927
    28.04.2022 10:58
    +4

    6, 7 и 8 — сплошное передёргивание. Перенос почему-то назван «переполнением». Как-то вообще скромно обойдён вопрос длинной арифметики, сдвигов и т.п.
    А команды x86 в принципе не допускали потери данных…
    Ещё можно было в AVR красиво генерить шим за 4 такта на канал)
    ld BL,-Z
    cp AL,BL
    rol AH
    ld BL,-Z
    cp AL,BL
    rol AH
    ...


  1. mpa4b
    28.04.2022 11:48
    +10

    Таким образом RISC-V легко превосходит ARM в плотности кода.

    Что-то прям вообще какое-то нагибалово беззастенчивое. B арме инструкция одна и размером 32 бита, а сжатые инструкции в RISC-V -- 16 бит каждая, итого суммарно 48 бит (6 байт), да ещё и с ограничениями на доступные регистры. Пожалуй в плотности кода RISC-V точно не превзойти ARM.

    Касательно условного исполнения -- мне кажется, всё проще. В рамки парадигмы "отсутствие флагов, трёхадресные команды" условное исполнение никак не вписывается. Так что скорее причина -- именно отсутствие флагов.

    Тем не менее, условное исполнение -- это был очень могучий оптимизатор во времена простых конвейерных процессоров без предсказателей переходов типа ARM7TDMI. Ну в общем-то и остаётся -- для всяких там cortex-M3 и подобных.

    Сложности с условным исполнением для OoO процессоров конечно есть, и лежат они скорее в той плоскости, что если обычная 3-адресная инструкция зависит по данным только от 2 регистров-источников, то такая же условная начинает зависеть ещё и от регистра флагов и заодно от регистра-приёмника, т.к. по сути она всегда записывает регистр-приёмник, но в зависимости от выполненности условия -- или вычисленным значением или предыдущим неизменным.

    Ещё есть вот такие соображения:

    1. Касательно простоты архитектуры RISC-V. Оно конечно так, но только если не смотреть на такие расширения как атомики (куча инструкций типа `AMOADD, AMOOR, AMOXOR` и проч. -- в дополнение к традиционным load-reserved/store-conditional. Зачем???) или то же векторное расширение. Лично я слабо знаком с векторными архитектурами, а чтение описалова этого векторного расширения создаёт стойкое ощущение что без такового знакомства там я мало что пойму. У создалось впечатление, что если базовый набор команд разработала небольшая группа людей, то все эти расширения уже начали разрабатываться 'комитетами' с соответствующими результатами. За примерами аналогичных ситуаций можно далеко не ходить, достаточно вспомнить ситуацию с разными Алголами в прошлом и посмотреть на текущую ситуацию с C++.

    2. Открытость и доступность для реализации архитектуры -- это безусловно хорошо. Но одно из следствий этого -- появления десятков и сотен всеразличных ядер, многие будут со своими собственными расширениями (например кто-то может захотеть приделать собственнные SIMD инструкции вместо монструозных стандартных векторных) и со своими собственными требованиями к компиляторам в части генерации оптимизированного кода. Результатом может стать (или уже становится) невообразимый бардак версий и патчей например того же gcc для каждой реализации архитектуры.


    1. byman
      28.04.2022 17:02
      +3

      Пожалуй в плотности кода RISC-V точно не превзойти ARM.

      Он и не превосходит. Набираете в гугле "risc-v code size vs arm size" и получаете ответ:

      Despite the RISC-V RVC extension and compiler optimizations, RISC-V code can be more than 11% larger than the ARM counter- part on embedded-domain benchmarks. :)


    1. byman
      28.04.2022 17:32
      +1

      У создалось впечатление, что если базовый набор команд разработала небольшая группа людей, то все эти расширения уже начали разрабатываться 'комитетами' с соответствующими результатами.

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


      1. mpa4b
        28.04.2022 18:08
        +1

        Ну как по мне, базовый RISC-V (типа IMC) вполне себе вкусный проц для какого-нибудь ембедеда. Опять же и роялти не надо платить (если самому запилить ядро или взять опенсорцное) :)


  1. mapnik
    28.04.2022 12:17
    +3

    Вообще не вижу особого смысла меряться плотностью кода. На 8086 вон можно в два байта (не считая подготовки указателей) скопировать 64 килобайта памяти в другие 64 килобайта памяти — и?.. Объявим x86 наипрогрессивнейшей архитектурой?


    1. mpa4b
      28.04.2022 12:22
      +1

      смысл в плотности кода есть, чем плотнее код тем эффективнее используется кеш инструкций.


      1. vassabi
        28.04.2022 13:12
        +1

        ... впору ставить отдельный "контроллер для сжатия инструкций" на этот кеш


      1. Gmugra
        28.04.2022 13:31
        +1

        Я очень далек от темы, поэтому вопрос от чайника: а насколько это важная проблема для современных CPU у которых и 100Mb кэша может быть?


        1. fortyseven Автор
          28.04.2022 14:03
          +1

          Важная так как мегабайты кэша только L3. Он сильно медленнее чем L1 и не очень подходит для хранения инструкций.


        1. mpa4b
          28.04.2022 15:44
          +1

          Крайне важная. размеры кеша инструкций L1 -- порядка 16-32 килобайт, и очень хорошо заметно на unroll'еном цикле, например, когда его размер начинает превышать размер кеша L1 инструкций: сразу падает производительность. Для случая менее плотного кода это случится раньше.


          1. Viknet
            28.04.2022 18:48
            +1

            В ARM64 бывает и вот так:


            The high-performance cores have an unusually large 192 KB of L1 instruction cache and 128 KB of L1 data cache and share a 12 MB L2 cache.

            Apple M1


        1. byman
          28.04.2022 17:24
          +2

          Чтобы заметить влияние плотности кода нужно чтобы критические циклы не вмещались в кэш первого уровня, т.е. где-то в 32К байта :) Для примера, весь код коремарка меньше 10К. К тому же в процессорах делается все, чтобы подзагрузка кода в кэш 1-го уровня (из кэшей других уровней) была как можно быстрее и желательно заранее. Так что небольшая разница в размере кода это ерунда.


      1. AMaxKaluga
        28.04.2022 14:18
        +1

        и пофиг на длительность исполнения самих инструкций....

        Туда смотри, сюда не смотри, здесь селедку заворачивали.⁠⁠


        1. mpa4b
          28.04.2022 15:47
          +1

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


    1. homm
      28.04.2022 14:17
      +1

      Вы приводите экстремальный пример. Для определения есть ли смысл, нужно рассматривать не примеры, а среднее влияние.


  1. homm
    28.04.2022 14:21
    +2

    Очень непоследовательное доказательство. Наша ISA в одном месте не хуже конкурент, ведь есть МОС. А вдругом даже лучше, потому что у конкурента только МОС где у нас специальные команды.

    Вы уж определитесь, МОС не хуже чем выделенные команды, или всё же немножечко хуже?


    1. fortyseven Автор
      28.04.2022 15:00
      +1

      Можно конкретнее? В тексте не совсем так написано. Скорее есть набросы на RISC-М и контр аргументы. Кажется, вы слишком упрощаете.


  1. AquariusStar
    28.04.2022 20:02
    +1

    По второму пункту. Длина команд в RISC-V может достигать до 40 байт. И даже оставлен резерв для более 40 байт длины. Это описано в спецификации. И длина команды по второй версии спецификации определяется в первом 16-разрядном слове. Причём определение длины команды имеет характерные особенности самосинхронизации.

    И по последним пунктам. Не стоит путать целочисленный перенос от переполнения... Так как в некоторых задачах перенос вполне нормальная операция. Например, для создания длинной арифметики. В некоторых языках программирования это есть.

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

    А оставшиеся четвёртые и пятые пункты можно долго рассуждать. Практика может привнести неожиданные сюрпризы. Но в одном я согласен, SIMD у x86/x86-64 — та ещё жестянка.

    P.S. Поправил длину байт. Промахнулся с расчётом.


    1. beeruser
      28.04.2022 21:34
      +1

      Длина команд в RISC-V может достигать до 40 байт.

      А ссылку на спецификацию можете предоставить?
      В спецификации расширения «C» я ничего такого не наблюдаю.
      riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf#page=79

      RISC-V standard compressed instruction
      set extension, named “C”, which reduces static and dynamic code size by adding short 16-bit
      instruction encodings
      for common operations.


      Не стоит путать целочисленный перенос от переполнения

      Последние пункты про переполнение, а не про перенос.
      Но в случае беззнаковых чисел это одно и то же.
      add t0, t1, t2 # t0 ← t1 + t2
      bltu t0, t1, overflow # перейти к overflow, если t0 < t1

      Условное исполнение удобно тем, что можно выполнить по флагам и всё такое.

      Если очень нужно, используются slt/slti. Ваш флаг будет в регистре и можно выполнять над ним любые операции. В Power есть специальные логические операции для работы с флагами. Такой вот «RISC».

      А слияние в макроинструкции я пока не вижу плюса.

      А вот разработчики процессоров — видят.
      Макроинструкции занимают один слот в ROB, к примеру.

      By reducing the number of instructions that must be executed, more work can be done with fewer resources. The idea behind macro-operation fusion is to combine multiple adjacent instructions into a single instruction. A fused instruction typically remains fused throughout its lifetime. Therefore fused instructions can represent more work with fewer bits, free up execution units, tracking information (e.g. in the rename unit), save pipeline bandwidth in all stages from decode to retire, and consequently save power.

      en.wikichip.org/wiki/macro-operation_fusion

      fortyseven
      У вас смысл переведённого искажён. Откуда вы взяли «даже»?
      С другой стороны, в лагере ARM отсутствие условных инструкций убивает производительность даже на простых микропроцессорах без OoOE (многие 32-разрядные ARM).

      И оригинал. Никаких «даже» тут нет.
      The counter-point to this from over at the ARM camp is that by not having conditional instructions you kill performance on simpler In-Order microprocessors (where 32-bit ARM would be used).

      Имеется в виду, что ARM лагерь утверждает, что без инструкций условного выполнения на простых in-order процессорах, производительность будет страдать.
      (потому как мало транзисторов для реализации эффективного предсказателя).


      1. fortyseven Автор
        28.04.2022 21:49

        Поправлю, это явно ошибка


      1. AquariusStar
        28.04.2022 21:59
        +1

        А ссылку на спецификацию можете предоставить?В спецификации расширения «C» я ничего такого не наблюдаю.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf#page=79

        RISC-V standard compressed instructionset extension, named “C”, which reduces static and dynamic code size by adding short 16-bitinstruction encodings for common operations.

        Вы по ссылке далеко уехали. Это описывается в начале документации. На 17-18 странице. Далее увидите картинку Figure 1.1: RISC-V instruction length encoding. Это как раз описывает декодирование длины команды. Кстати, при сравнении между спецификациями 2.0 и 2.2 версий длина стала короче в последней. Теперь 192 бита (24 байт) вместо 320 бит (40 байт). Но резерв на большие длины команд остался.


  1. emusic
    29.04.2022 23:04

    Перевод instruction как "инструкция" прямо глаза режет. Вроде ж начали с "команд", зачем чередовать?


    1. fortyseven Автор
      30.04.2022 11:02

      Как-то увлёкся :)