Недостатки RISC-V

Автор оригинала: Erin Alexis Owen Shepherd
  • Перевод
Изначально я написала этот документ несколько лет назад, будучи инженером по проверке ядра исполнения команд (execution core verification engineer) в ARM. Конечно, на моё мнение повлияла углублённая работа с исполнительными ядрами разных процессоров. Так что делайте на это скидку, пожалуйста: может, я слишком категорична.

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

Статья изначально описывала набор команд RISC-V 2.0. Для версии 2.2 в ней сделаны некоторые обновления.

Оригинальное предисловие: немного личного мнения


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

Например, вот код C:

int readidx(int *p, size_t idx)
{ return p[idx]; }

Это простой случай индексирования массива, очень распространённая операция. Так выглядит компиляция для x86_64:

mov eax, [rdi+rsi*4]
ret

или ARM:

ldr r0, [r0, r1, lsl #2]
bx lr // return

Однако для RISC-V необходим такой код:

slli a1, a1, 2
add a0, a1, a1
lw a0, a0, 0
jalr r0, r1, 0 // return

Симплификация RISC-V упрощает декодер (т. е. фронтенд CPU) за счёт выполнения большего количества инструкций. Но масштабирование ширины конвейера — сложная проблема, в то время как декодирование слегка (или сильно) нерегулярных инструкций хорошо реализуется (основная трудность возникает, когда трудно определить длину инструкции: это особенно проявляется в наборе команд x86 с многочисленными префиксами).

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

Мы должны отличать «сложные» специфические инструкции CISC-процессоров — усложнённые, редко используемые и малооэффективные инструкции — от «функциональных» инструкций, общих для процессоров CISC и RISC, которые объединяют небольшую последовательность операций. Последние используются часто и с высокой производительностью.

Посредственная реализация


  • Почти неограниченная расширяемость. Хотя это и является целью RISC-V, но это создаёт фрагментированную, несовместимую экосистему, которой придётся управлять с особой осторожностью
  • Одна и та же инструкция (JALR) используется и для вызовов, и для возвратов и для косвенно-регистровых переходов (register-indirect branches), где требуется дополнительное декодирование для предсказания ветвей
    • Вызов: Rd = R1
    • Возврат: Rd = R0, Rs = R1
    • Косвенный переход: Rd = R0, RsR1
    • (Странный переход: RdR0, RdR1)
  • Кодирование с переменной длиной поля записи не самосинхронизируется (такое часто встречается — например, аналогичная проблема у x86 и Thumb-2, — но это вызывает различные проблемы как с реализацией, так и с безопасностью, например, возвратно-ориентированное программирование, то есть атаки ROP)
  • RV64I требует расширения знака для всех 32-разрядных значений. Это приводит к тому, что верхнюю половину 64-битных регистров становится невозможно использовать для хранения промежуточных результатов, что ведёт к ненужному специальному размещению верхней половины регистров. Более оптимально использовать расширение нулями (поскольку оно уменьшает число переключений и обычно его можно оптимизировать путём отслеживания «нулевого» бита, когда верхняя половина, как известно, равна нулю)
  • Умножение опционально. Хотя быстрые блоки перемножения могут занимать довольно существенную площадь на крошечных кристаллах, но всегда можно использовать чуть более медленные схемы, которые активно используют существующий ALU для многократных циклов умножения.
  • У LR/SC строгое требование к поступательному продвижению для ограниченного подмножества применений. Хотя это ограничение довольно жёсткое, оно потенциально создаёт некоторые проблемы для небольших реализаций (особенно без кэша)
    • Это кажется заменой инструкции CAS, см. комментарий ниже
  • Биты закрепления в памяти (sticky bits) FP и режим округления находятся в одном регистре. Это требует сериализации канала FP, если выполняется операция RMW для изменения режима округления
  • Инструкции FP кодируются для 32, 64 и 128-битной точности, но не 16-битной (что значительно чаще встречается в аппаратном обеспечении, чем 128 бит)
    • Это можно легко исправить: код размерности 0b10 свободен
    • Обновление: в версии 2.2 появился десятичный заполнитель, но нет заполнителя половинной точности. Уму непостижимо.
  • То, как значения FP представлены в файле регистра FP, не определено, но наблюдаемо (через load/store)
    • Авторы эмуляторов вас возненавидят
    • Миграция виртуальных машин может стать невозможной
    • Обновление: версия 2.2 требует более широких значений NaN-boxing

Плохо


  • Отсутствуют коды условий, а вместо них используются инструкции compare-and-branch. Это не проблема сама по себе, но последствия неприятные:
    • Уменьшение пространства кодирования в условных переходах из-за необходимости кодирования одного или двух спецификаторов регистров
    • Нет условного выбора (полезно для очень непредсказуемых переходов)
    • Нет сложения с переносом / вычитания с переносом или заимствованием
    • (Обратите внимание, что это всё равно лучше, чем наборы команд, которые пишут флаги в регистр общего назначения, а затем переходят на полученные флаги)
  • Кажется, что высокоточные счётчики (аппаратных циклов) требуются в непривилегированной ISA. На практике, предоставление их приложениям является отличным вектором для атак по сторонним каналам
  • Умножение и деление являются частью одного и того же расширения, и кажется, что если одно реализовано, то и другое тоже должно быть. Умножение значительно проще, чем деление, и распространено на большинстве процессоров, а деление нет
  • Нет атомарных инструкций в базовой архитектуре набора команд. Всё более распространёнными становятся многоядерные микроконтроллеры, так что атомарные инструкции типа LL/SC обходятся недорого (для минимальной реализации в рамках единого [многоядерного] процессора нужен всего 1 бит состояния процессора)
  • LR/SC находятся в том же расширении, что и более сложные атомарные инструкции, что ограничивает гибкость для небольших реализаций
  • Общие атомарные инструкции (не LR/SC) не включают примитив CAS
    • Смысл в том, чтобы избежать необходимости в инструкции, которая читает пять регистров (Addr, CmpHi:CmpLo, SwapHi:SwapLo), но это, вероятно, наложит меньше накладных расходов на реализацию, чем гарантированное продвижение вперёд LR/SC, которое предоставляется в качестве замены
  • Предлагаются атомарные инструкции, которые работают на 32-разрядных и 64-разрядных величинах, но не 8-ми или 16-битных
  • Для RV32I нет способа передать значение DP FP между целым числом и регистровым файлом FP, кроме как через память, то есть из 32-битных целочисленных регистров нельзя составить 64-битное число двойной точности с плавающей точкой, придётся сначала записать промежуточное значение в память и загрузить его в регистровый файл оттуда
  • Например, у 32-битной инструкция ADD в RV32I и 64-битной ADD в RVI64 одинаковые кодировки, а в RVI64 добавляется ещё и другая кодировка ADD.W. Это ненужное усложнение для процессора, который реализует обе инструкции — было бы предпочтительнее вместо этого добавить новую 64-битную кодировку.
  • Нет инструкции MOV. Мнемокод команды MV транслируется ассемблером в инструкцию MV rD, rS-->ADDI rD, rS, 0. Высокопроизводительные процессоры обычно и так оптимизируют инструкции MOV, широко задействуя при этом переупорядочивание команд. В качестве канонической формы команды MV в RISC-V была выбрана инструкция с непосредственным 12-битовым операндом.
    • При отсутствии MOV инструкция ADD rD, rS, r0 фактически становится предпочтительнее канонической MOV, поскольку её проще декодировать, а операции с нулевым регисторм (r0) в CPU обычно оптимизированы

Ужасно


  • JAL тратит 5 бит на кодирование регистра связи, который всегда равен R1 (или R0 для переходов)
    • Это означает, что RV32I использует 21-битные смещения ветвей (branch displacement). Это недостаточно для больших приложений — например, веб-браузеров — без использования нескольких последовательностей команд и/или «островов ветвей» (branch islands)
    • Это ухудшение по сравнению с версией 1.0 архитектуры команд!
  • Несмотря на большие усилия на равномерное кодирование, инструкции load/store кодируются по-разному (меняются регистр и непосредственные поля)
    • Видимо, ортогональность кодирования выходного регистра была предпочтительнее ортогональности кодирования двух сильно связанных инструкций. Этот выбор кажется немного странным, учитывая, что генерация адресов более критична по времени
  • Нет команд загрузки из памяти со смещениями регистров (Rbase+Roffset) или индексов (Rbase+Rindex << Scale).
  • FENCE.I подразумевает полную синхронизацию кэша инструкций со всеми предыдущими хранилищами, с ограждением (fenced) или без него. Реализациям нужно или очищать весь I$ на ограждении, или выискивать D$ и накопительный буфер (store buffer)
  • В RV32I чтение 64-битных счётчиков требует двукратного чтения верхней половины, сравнения и ветвления в случае переноса между нижней и верхней половиной во время операции чтения
    • Обычно 32-разрядные ISA включают в себя инструкцию «чтение пары специальных регистров», чтобы избежать этой проблемы
  • Нет архитектурно определённого пространства hint-кодирования, так чтобы инструкции из этого пространства не вызывали ошибку на старых процессорах (обрабатывались как NOP), но что-то делали на самых современных CPU
    • Типичные примеры чистых «хинтов NOP» — такие вещи, как spinlock yield
    • На новых процессорах также реализованы более сложные хинты (с видимыми побочными эффектами на новых процессорах; например, инструкции проверки границ x86 кодируются в hint-пространстве, так что бинарники остаются обратно совместимыми)

Комментарии 53

    –8
    Процессоры с крайне минималистичными наборами инструкций никогда не смогут сравняться производительностью со сложными процессорами, где целые последовательности вычислений реализованы на железном уровне. Т.е. с точки зрения эффективности RISC-V уже в проигрышном положении.
    Также непонятно, какой будет выигрыш с точки зрения энергопотребления, если для выполнения одной и той же работы надо будет выполнить в разы больше инструкций — можно понизить частоту на CISC до эквивалентного уровня производительности и тоже получить холодный процессор.
      +10
      Нет, меньше инструкций — больше переиспользование транзисторов: один и тот же набор выполняет кучу CISC инструкций, соотвественно, там где у CISC холодные блоки, простаивают и ждут своей уникальной инструкции, в RISC добавляют ядра/конвееры. Плюс к этому, за счёт коротких инструкций дискретность выше: там где у CISC длинные сложные схемы и низкие частоты (это не говорит о низкой производительности) выравнивание до границы такта съедает больше времени, чем вытягивание до границы такта более высокочастотной схемы (это как посекундная и поминутная тарификация). На практике, в эпоху тёмного кремния и близкого предела к границе частот — всё не так однозначно.
        –1
        Все эти идеалы всего лишь идеалы. CISC поголовно суперскалярные out of order процессоры. Там нечему простаивать. Маленькие инструкции RISC делают только хуже — у тебя всегда будет много мелких инструкции и быстрее их выполнять так просто не получится. Напротив, CISC инструкцию каждую можно засунуть в железо и именно так поступают в x86 процессорах. Вся эта экономия на транзисторах хороша, где бюджеты маленькие. RISC-V наверное тут вполне сгодится, но пост о том, что сделать что-то большее скорее всего не получится именно из-за ISA. Туда же миф о том, что сложные инструкции сложно декодировать, поэтому давайте тут сэкономим лучше. Декодер это мизерная часть современного процессора, которая не представляет никаких сложностей давно.
          +3
          x86 это RISC процессор с микропрограммой, соглашусь с вами «именно так поступают в x86 процессорах». Последний CISC x86, это был процессор от Cyrix.
          Маленькие инструкции RISC делают только хуже

          Нет, благодаря маленьким инструкциям возможна параллельная обработка и нет границ цисковых комманд, в итоге у CISC любая инструкция не меньше 1 такта, а в RISC цисковые комманды могут занимать и 0.5 такта в потоке и любые дробные значения, что было очень неплохой маркетинговой фишкей во времена Пентиумов.

          Вы плаваете в терминологии и железе.

          P.S. Про все новомодные AVX — не владею темой, но это уже и не x86, строго говоря, даже 64bit, это уже AMD64 архитектура. Фактически, современные процессоры, это SystemOnChip, там и видео ускорители и DSP и контроллеры памяти, нейронные ускорители и т.п. Если вы разные блоки плюсуете к x86, тогда почему АPU целиком не называть x86?
            +3
            Декодер это мизерная часть современного процессора, которая не представляет никаких сложностей давно

            Мне кажется это сомнительное утверждение. В последнее время часто встречаются статью в которых пытаются либо разгромить либо расхвалить RISC-V, но авторы и тех и других согласны в одном — декодер (точнее — блок предсказания) современных CISC процессоров мало того, что заниемает много места на кристалле, так его сложность перестала быть управляемой. Вспомните Spectre и Meltdown. В этом смысле VLIW — архитектура почти без декодера, где всё планирование выполняет компилятор.

            Ну и по мне, отсутствие оптимизирующего компилятора это решаемая, со временем, проблема не требующая аппаратных изменений.
              +3
              Процессоры Itanium, EPIC и VLIW умерли по нескольким причинам, говорит Паттерсон:

              Непредсказуемые ветвления, усложняющие планирование и упаковку параллельных операций в командные слова VLIW.
              Непредсказуемые промахи кэша замедляли выполнение и приводили к переменным задержкам выполнения.
              Наборы команд VLIW раздували объём кода.
              Оказалось слишком сложно создать хорошие оптимизирующие компиляторы для машин VLIW.

              Пожалуй, самый известный в мире специалист по компьютерным алгоритмам Дональд Кнут заметил: «Подход Itanium… казался таким великолепным — пока не выяснилось, что желанные компиляторы по сути невозможно написать».
              habr.com/ru/post/411989
                –1
                Пожалуй, самый известный в мире специалист по компьютерным алгоритмам Дональд Кнут заметил: «Подход Itanium… казался таким великолепным — пока не выяснилось, что желанные компиляторы по сути невозможно написать»


                Itanium умер так и не родившись почти 20 лет назад. С тех пор прогресс шагнул вперед довольно сильно. Если неполучается сделать предсказатель на четкой логике, значит нужно сделать его на нейросетях — обучаемым под конкретное приложение (в этой отрасли сейчас програсс скачет семимильными шагами). Оптимизирующий компилятор под VLIW (Эльбрус), кстати, написан и работает весьма не плохо. Так, что товарищу Кнуту пора бы уже перестать будоражить сознание молодых и горячих :-).
                  0
                  Itanium умер в 2017 году, и для него тоже был написан компилятор в полном соответствии с прогрессом. И этот компилятор хорошо хорошо работал только на задачах, что и так хорошо работают на обычном CPU c SIMD, а еще лучше работают на GPU. Точно так же работает и компилятор Эльбруса, при теоретической 21 параллельно исполняемой операции среднее число в реальности 2-3 операции, и максимум 5-10 на счетных задачах с векторами.
                  +1
                  Инженеры МЦСТ, смотрят на ваш коментарий с недоумением. А затем продолжают каждый год выпускать компилятор, который всё повышает и повышает быстродействие уже выпущенного железа.
                    +5
                    Инженеры Intel, смотрят на ваш комментарий с недоумением. Они точно также написали компилятор и каждый год все повышали и повышали. А потом оказалось что обычные Xeon дешевле, быстрее и компиляторы для них лучше, многоядерность гораздо универсальнее, а Itanium никогда достигнет их ожиданий. И GPU справляются еще лучше с теми задачами для которых он предназначался.
                      +2
                      Помоему Интел просто убил конкурента внутри себя. Чисто маркетинговое решение, не имеющее отношение к технологии.

                      Есть еще один момент. Интел заложник своего x86 ISA — везде и всюду нужно обеспечивать совместимость вниз. Выкатив на рынок Itanium, который мог исполнять x86 только в режиме эмуляции (или b2b компиляции) ни к чему доброму привести не могло — с экономической точки зрения.
                        +1
                        С продажами в несколько десятков тысяч в год против миллионов x86-64 никаким конкурентом Itanium не являлся. С такими продажами улучшать его технологии было слишком дорого и не имело никакого смысла. И Itanium стал просто технологически отставать от массовых x86-64.

                        Есть другой момент. Выкатив на рынок Itanium, который мог в эмуляции исполнять PA-RISC и MIPS, и которые заменил, а так же заменил и Alpha. И вроде бы все это вело к добру с экономической точки, но что то пошло не так.
                        +1
                        У инженеров Itanium всё-таки была другая задача — убить рынок x86 и разорить AMD. По сути стать монополистом в сфере 64-х бит. Как только стало понятно, что ничего из запланированного не будет достигнуто, проект закопали.
                        У инженеров МЦСТ задача — обеспечить приемлемую производительность и стратегическую безопасность в рамках импортозамещения. И они со всеми пунктами справляются.
                          +1
                          Intel не собиралась терять рынок x86. Проект Itanium как раз по сговору закопал всех прочих конкурентов — PA-RISC, MIPS, Alpha. И в свою очередь проиграл более дешевым и чаще обновляемым х86. Они же практически закопали и SPARC c PowerPC.
                –1
                На практике, в эпоху тёмного кремния и близкого предела к границе частот — всё не так однозначно.
                Я и не говорю что все однозначно ;) Свои плюсы есть у разных подходов. Просто RISC-V, вместо того, чтобы сделать однозначно лучшую архитектуру, достиг всего лишь уровня «не все однозначно».
                Что касается «больше ядер/конвееров» — вот в GPU много ядер, и они даже весьма универсальные — но их используют только для специализированных вычислений, а не в качестве ядер процессоров общего назначения.
                Дискретность выше, и частоты потенциальные выше, но!.. Вы правда полагаете, что сможете вычислить раунд AES быстрее на RISC-V, чем это сделает AES-NI? Да никогда в жизни. Согласен, это грубый пример, но принцип общий — если для CISC в кремнии есть специализированная логика для быстрого вычисления rdi+rsi*4, то на RISC это надо программировать! Это как сравнить нативное исполнение сложной инструкции с эмуляцией микрокодом — микрокоду кремний не догнать, как ни крути.
                  –1
                  если для CISC в кремнии есть специализированная логика для быстрого вычисления rdi+rsi*4, то на RISC это надо программировать! Это как сравнить нативное исполнение сложной инструкции с эмуляцией микрокодом — микрокоду кремний не догнать, как ни крути


                  Дык в том то весь прикол, что современный CISC это обертка для RISC + микропрограммы. Нет в интелловских процессорах куска кремния (выделенной транзисторной схемы) для rdi+rsi*4, есть микропрограмма из пары десятков микрокомманд исполняемых на скрытом от пользователя (программиста) RISC ядре (или нескольких ядрах).
                    +1
                    Конкретно в данном случае, думаю, Вы ошибаетесь. Операция смещения (умножения на 4) настолько дешева в аппаратной реализации, что нет никакого смысла (речь про высокопроизводительные процессоры, а не сверхэкономичные решения) выполнять вычисление rdi+rsi*4 микрокодом за несколько тактов, когда этот адрес можно легко вычислить за один (такт) прямой аппаратной реализацией.
                      –1
                      Помимо чисто арифметической операции там есть масса вспомогательных uops. Вот цитата из Wikipedia про микрокод:

                      Microprograms consist of series of microinstructions, which control the CPU at a very fundamental level of hardware circuitry. For example, a single typical horizontal microinstruction might specify the following operations:

                      Connect register 1 to the A side of the ALU
                      Connect register 7 to the B side of the ALU
                      Set the ALU to perform two's-complement addition
                      Set the ALU's carry input to zero
                      Store the result value in register 8
                      Update the condition codes from the ALU status flags (negative, zero, overflow, and carry)
                      Microjump to microPC nnn for the next microinstruction


                      Так, что приведенная вами rdi+rsi*4 может выйти в не один десяток uops. К сожалению, Интел не разглашает подробности реализации x86 ISA.

                      Ну и что бы совсем не было сомнений, в статье Intel Microcode пишут, что начиная с P6 (конец 90-х) во всех процессорах Intel микрокод может быть пропатчен. А значит аппаратной реализации инструкции rdi+rsi*4 нет, есть набор микропрограмм, состоящих из примитивных операций (uops). Как тут уже многократно отмечалось, все интелловские процессоры это своего рода RISC с нахлабученой сверху x86 ISA.

                      Вот еще цитата из той же Вики:
                      In the P6 and later microarchitectures, lists of x86 instructions are internally converted into simpler RISC-style micro-operations
                        +1
                        Имхо из-за названия микрокоманды вы неверно ставите их почти тождественными по значению просто командам, но это не совсем так. Эти микрокоманды ближе по логике действия к установке значения на конкретных шинах. Точно такая же микрокоманда может включать сложнейший блок явно являющейся элементом CISC архитектуры и этот способ запуска не сделает её RISC'ом.
                    –1
                    Кстати, по поводу GPU. В Википедии пишут:

                    Nvidia plans to use RISC-V to replace their Falcon processor on their GeForce graphics cards

                    И дана ссылка на NVIDIA RISC V Evaluation Story
                      +1
                      Микроконтроллер видяхи? Так там и ARM или x86 можно поставить, если будет економический смысл, но к вычислительным ядрам GPU это вообще никакого отношения не имеет.
                      +1
                      Раунды AES опасно вычислять на процессорах без аппаратной поддержки AES (типа той же AES-NI)… Каждый лукап в таблицу по секретному индексу — потенциальная утечка и цель для timing attack (из-за кешей). Есть конечно программный AES без таблиц, в виде bit-slicing, но там вынужденно приходится считать сразу много блоков подряд и во весь рост встаёт вычисление мультипликативной инверсии (SubBytes() в AES) без таблиц.
                        0

                        Я подозреваю, что аппаратная поддержка AES была сделана ради TXT/SGX, чтобы шифрование памяти анклава на лету сделать. А потом уже сделали доступ к этому блоку из ABI, потом и TME/MKTME.
                        Что касается защиты от тайминг-атак — видел я где-то недавно статью, где предлагалось защищать критичные к этому участки кода внутрь транзакций TSX и "прибивать" интересующие линии кеша к этим транзакциям. Причем именно на примере AES и его lookup tables.

                    +2
                    Я не большой специалист, но могу сделать предположение, что если современные CISC процессоры (Intel) которые фактически являются RISC процессорами на которых исполняется микрокод (каждая инструкция x86 это несколько десятков, а то и сотен микроинструкций), способны к тому, на что они способны, то почему нелья избавиться от этой ширмы и дать компилятору возможность генерировать программу из набора самых примитивных операций, которые исполняясь на RISC ядре будут исполняться в массовом параллелизме и меньшими задержками на синхронизацию (за счет более тесных и коротких связей) достигая при этом максимальной занятости подсистем (транзисторов) вычислительного ядра. Помоему это то, к чему стремится RISC-V. С одной стороны такой минимализм накладывает много работы на плечи компилятора, но с другой, результирующий код может быть лучше оптимизирован, чем код из «крупных блоков» (x86 инструкций).
                      –4
                      Современные процессоры не RISC. С каждым поколением x86 все больше «сложных» инструкций превращаются из нескольких микроопсов в одну, т.е. полная железная реализация. И в этом как раз сакральный смысл CISC архитектуры, возможность подобной оптимизации. «x86 внутри на самом деле RISC» популярный миф, который не имеет ничего общего с реальностью.

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

                      Собственно, в этом весь пост. Вместо того, чтобы перенять весь опыт процессоростроения, RISC-V построен на устаревших понятиях о том, какой должна быть ISA, что делает, по сути, невозможным построение высокопроизводительного процессора. Весь современный опыт говорит, что современная высокопроизводительная ISA должна как раз иметь кучу специализированных инструкций. Это именно то, что компиляторы отлично умеют использовать. А дальше пусть работает out of order исполнение, предсказания и прочая магия. Если почитать, именно таковой является та же ARMv8, которую все хвалят. И от RISC идеалов там ушли довольно сильно.
                        0
                        А дальше пусть работает out of order исполнение, предсказания и прочая магия.

                        Меня лично вот эта «магия» сильно смущает. Я бы предпочел минималистичный ISA, а всю магию вынести в компилятор. Изменения в компиляторе, при определенном опыте, доступны программисту, а «магия» под покровом CISC — нет.

                        Вообще, в RISC я вижу одну серьезную проблему, которая почти не раскрыта в статье — большой обьём кода (видимо подразумевается, что читателю и так все понятно). Производительность процессоров сейчас во многом ограничена пропускной способностью шин: внутренних, ведущих к кэш памяти и внешних — к RAM. В этом смысле СISC архитектура выигрывает потому, что в одну инструкцию упаковывается больше «полезной работы», а значит меньше кода нужно передавать по шинам. Разработчики ARM даже отказались от своего стандартного набора команд и полностью перешли на укороченный (по размеру) набор инструкций Thumb в том числе и для того, что бы повысить эффективность исполняемого кода — небольшие по размеру команды выполняющие сразу много операций, даже SIMD утолкали. Некоторые коментаторы предлагают сжимать код алгоритмами сжатия данных (привет LZW), передавать его в таком виде по шинам и разжимать только в дешифраторе. Звучит бредово, но пока шина является узким местом — это действительно может сработать. И именно из-за этого CISC, по производительности, скорее всего будет выигрывать еще долгое время.

                        Еще раз вспомню VLIW, где одной инструкцией кодируется большое количество операций. В Эльбрус-2000, если я правильно понимаю, может исполняться до 21-й операции на инструкцию, при этом — минималистичный дешифратор (b2b компиляцию не рассматриваем). Отсюда и кристалл небольших размеров и низкое энергопотребление при весьма высокой производительности.

                          +5
                          Creker вас вводит в заблуждение.
                          Если современные процессоры x86 не RISC (хотя понятно, что речь про модуль исполнения этого набора комманд), то тогда и не CISC, а System On Chip, то есть набор разнородных микропроцессоров и специализированных блоков, включая потоковые и массивно-параллельные процессоры, он валит всё под один ярлык. Так же, соврешенно очевидно, что набор комманд x86 по прежнему исполняется как (RISC+микрокод). Фактически идёт подмена понятий.
                          При этом, вам тоже следует разбираться более тонко в архитектурах процессоров. Существует такая вещь как стековый процессор, вот уж у кого как можно меньше инструкций и вы не совсем правы насчёт размера кода (Статься про стековые процессоры). А вот по поводу RISC… это не просто сокращённый набор комманд, это идеология: каким должен быть этот набор комманд, помимо того, что он маленький, например — все инструкции работы с памятью считаются медленными, поэтому набор комманд RISC не должен содержать комманд работы с памятью, помимо загрузки и выгрузки в регистр, фактически, там — целая иделогия. А уж терминов после наплодили море, на все вкусы (risc, cisc, zisc, misc, oisc и т.п.) и за каждым что-нибудь стоит.
                            0
                            Под oisc вы имеете в виду вот это: en.wikipedia.org/wiki/One_instruction_set_computer?
                              +1
                              Только хотел высказать идею использования Машины Тьюринга из одной инструкции и бесконечной памяти как «ультимативный RISC», а оказывается уже все давно придумано до нас. :-)
                          0
                          Разработчики ARM даже отказались от своего стандартного набора команд и
                          полностью перешли на укороченный (по размеру) набор инструкций Thumb в том числе

                          А можно более подробно об этом? Вроде для ARM 32bit Thumb и обычные инструкции сосуществуют, а в их следующей архитектуре Aarch64 вообще никакого thumb режима не предусмотрено?

                            +1
                            В Cortex-M остался только Thumb-2 (как раз по причине пропускной способности шины). Про Aarch64 ничего не знаю, но смею предположить, что там и в помине нет предикатного исполнения инструкций, что долгое время являлось основным признаком ARM ISA.
                            +1
                            Полностью перешли на Thumb-2 (смесь 16- и 32-битных команд в новой кодировке) перешли только в процессорах Cortex-M. Все остальные (в 32-битном режиме, если речь про 64-битные) продолжают поддерживать как ARM, так и Thumb режимы.
                            +3
                            Как удобно сравнивать всё с интелом и армом. На их фоне что угодно будет устаревшим и малопроизводительным. Кто сделал хуже интела или арма — тот лузер.

                            Ну да, и интел, и арм вкладывали и вкладывают дикие десятки (сотни, тысячи?) миллиардов баксов в течение многих десятков лет в развитие своих архитектур, в R&D, в зарплаты учёных и инженеров-разработчиков. Без сравнимых вложений, очевидно, их с пьедестала не сдвинуть просто так. А процессор, совместимый с архитектурой risc-v, может написать буквально один человек за пару месяцев на HDL.

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

                            risc-v молодцы уже тем, что местами пошатали зажравшийся arm (и mips'у тоже досталось) по сути вообще без затрат бабла :)
                            0
                            почему нелья избавиться от этой ширмы и дать компилятору возможность генерировать программу из набора самых примитивных операций, которые исполняясь на RISC ядре будут исполняться в массовом параллелизме и меньшими задержками на синхронизацию (за счет более тесных и коротких связей) достигая при этом максимальной занятости подсистем (транзисторов) вычислительного ядра.


                            Я думаю, одна из причин — совместимость. Сейчас в разных процессорах может быть разное устройство этих «скрытых» RISC-процессоров. У них может быть разное количество внутренних регистров, возможно, разная логика предсказания переходов и т.п. Если поступать так как вы предлагаете, то надо будет компилировать каждую программу под каждую новую модель процессора отдельно. И не только компилировать, но и для каждой модели делать отдельный компилятор, который будет учитывать ее особенности.
                              0
                              Если поступать так как вы предлагаете, то надо будет компилировать каждую программу под каждую новую модель процессора отдельно. И не только компилировать, но и для каждой модели делать отдельный компилятор, который будет учитывать ее особенности.


                              Во-первых, не существует платформы/архитектуры без компилятора. Во-вторых, в среде UNIX/Linux перекомпиляция исходных кодов это не проблема, а добродетель. И втретьих, проблема совместимости железа решается с помощью Java (Android вам в пример), байт-код которой не зависит о разношерстного железа имеющегося на рынке. Т.е. совсместимость пользовательских приложений можно обеспечить спомощью Java или подобного механизма, а системное ПО (в том числе и интерпретатор байт-кода) обязятельно должно быть скомпилированно оптимизирующим компилятором под конкретную платформу. С моей дилетанской точки зрения это самый верный путь.
                                0
                                Есть и более горячие головы, в том числе в России, которые предлагают весь код хранить в промежуточном виде аля ассемблер. Как байт код LLVM IR, например. И компилить под нативную платформу по мере надобности, а там кешировать, но сохранять байт код и ABI. Поговаривают, что так Apple уже практикует и рекомендует для MacOS и iOS. Потом бац и весь код работает не на х86, а на ARM или RISC-V.
                                  +1
                                  У яблочников это называется Fat Binary, и уже давно. Помоему это банальное запихивание в один ELF сразу нескольких текстов собранных под различные ISA. Или вы имеете в виду что-то другое?
                                    0
                                    Про другое, про IR, про промежуточный код низкого уровня, но не машинный. То есть когда приложение потребуется для установки на конкретном железе, то идёт запрос на компиляцию из IR под это железо со всеми доплючами и аппаратными оптимизациями. LLVM под это дело почти идеально годится – пишем на любом языке, получаем универсальный IR код, а дальше бекэнд-компилятор под платформу затачивает. Для РИСКов самое то, потому как IR проектировался под них. Даже игроделы будут рады – для них есть SPIR-V для GPU.
                                      –1
                                      Так этож Just-in-Time компиляция, очень похоже на Android Run Time (ART). Прежде Dalvik в Android-е был интерпретатором Java, ART пришедший ему на смену — компилирует пользовательские приложения (Java байт-код) прямо в native и исполняет. Похоже в Apple решили не отставать, но за основу взяли IR. Очень интересно.
                                        0
                                        Общая идея похожа. Разница в поддержке языков и приоритете стратегии компиляции Ahead-of-Time над JIT. И тут байт код Java сильно проигрывает тому, сколько языков уже входит в обойму LLVM. Особенно таких как С, Rust и Go.

                                        Тот, кто создаст платформу приложений на этой основе – будет новым Apple/Microsoft/Google :)
                                  0
                                  Во-первых, не существует платформы/архитектуры без компилятора.


                                  Так платформы/архитектуры, а не модели процессора! И не один компилятор, а полный набор для всех более или менее популярных языков.

                                  Во-вторых, в среде UNIX/Linux перекомпиляция исходных кодов это не проблема, а добродетель.


                                  Компилятор надо сначала написать. Каждая новая модель процессора будет требовать собственный набор компиляторов для разных языков.

                                  И втретьих, проблема совместимости железа решается с помощью Java (Android вам в пример), байт-код которой не зависит о разношерстного железа имеющегося на рынке.


                                  Байткод, положим, не зависит. Но для каждого железа (платформы) нужна своя виртуальная машина. А с таким подходом будет нужна для каждой новой модели процессора.

                                  а системное ПО (в том числе и интерпретатор байт-кода) обязятельно должно быть скомпилированно оптимизирующим компилятором под конкретную платформу


                                  Интерпретатор байткода надо будет не просто скомпилировать под каждую новую платформу, его нужно будет написать под каждую новую модель процессора — там же будет новая система команд, фактически, а в виртуальной машине присутствует jit-компилятор. И этот компилятор должен будет генерировать нативный код для именно этой модели процессора.
                                0
                                На сколько я (по дилетански) понимаю сейчас все упирается в шину памяти.
                                Все процессоры сейчас быстро выполняют инструкции первого-второго уровня кеша и ждут когда наконец прийдут новые команды (а не только данные) из оперативки.

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

                                Если разбить инструкции на микроиснтрукции — процессоры будут работать дольше. Вне зависимости от своей частоты или архитектуры. Потому что шина памяти не справляется уже.
                                  –1
                                  Шины памяти тоже растут вширь. В новом MacPro используется 4096-разрядная шина.

                                  А можно пойти встречным путём – максимально использовать сжатые RISC-V инструкции при компиляции, то есть в 64 битный пакет разом влезет четыре 16-битные команды.
                                  (Chapter 16 “C” Standard Extension forCompressed Instructions, Version 2.0)
                                    +1
                                    > Шины памяти тоже растут вширь.

                                    В терминах интернета — пинг важнее ширины канала. Тайминги памяти растут.

                                    > А можно пойти встречным путём – максимально использовать сжатые RISC-V инструкции при компиляции, то есть в 64 битный пакет разом влезет четыре 16-битные команды.

                                    На сколько я знаю ассемблер интелла, и о чем явно говорится в этой статье — 64 бита инструкций инелла легко могут быть 128 битами РИСКа или даже 256 битами. Это с учетом предложенного сжатия.
                                      0
                                      Так для этого ссылку и дал на спецификацию, что б не гадать про РИСКи))
                                      Там прям по тексту: размер инструкции 16 бит.
                                      Зачем приводить гипотетические 128/256?
                                        –2
                                        Статья начинасется с примера, когда 2 инструкции стают в риске 4. Если покопаться то можно найти случаи когда инструкций будет не в 2 раза больше, а в 6 или 10. Особенно в интеловских AVX или паках инструкций созданных для кодирования и шифрования.
                                0
                                На самом деле так и есть, современные компиляторы не используют устаревшие CISC инструкции х86, а только подмножество команд которые отлично укладываются на RISC ядро новых интел и амд х64, и соответствующе оптимизируют.
                              +13
                              Тот случай, когда оригинал понятнее перевода:

                              «Это приводит к ненужному переключению» — на самом деле «This produces unnecessary top-half toggling»
                              «Биты закрепления в памяти FP» => «FP sticky bits» (какие нахрен 'биты закрепления'???)
                              «При отсутствии MOV инструкция MOV rD, rS, r0» — очевидно ADD rD,rS,r0 (как и написано в оригинале)

                                +9
                                Может у RISC-V и есть недостатки, но то что он сдвинул монополию ARM в процессорных ядрах это факт.

                                — ARM изменил политику лицензирования
                                www.arm.com/products/flexible-access/product
                                Вкратце берите что хотите, платить будите потом.

                                — RISC-V поддерживается в среде разработки IAR
                                www.iar.com/iar-embedded-workbench/#!?architecture=RISC-V&currentTab=partners
                                (из 7 партнеров — двое из России)

                                — Qualcome инвестировал $60M в RISC-V
                                www.hpcwire.com/2019/06/07/qualcomm-invests-in-risc-v-startup-sifive

                                — Alibaba выпустил свой чип на RISC-V
                                www.eetimes.com/document.asp?doc_id=1334966#

                                  0
                                  Спасибо за шикарный анализ. Испытал архитектурно-эстетическое удовольствие от чтения. :)
                                    0

                                    Половина аргументов скорее адекватны, половина (примерно) спорны. Например:


                                    • FENCE.I имеет резерв на более тонкое управление (зачем-то оставили аж 22 бита), и это явно указано. Возможно, добавят задание более мгких вариантов.
                                    • Чтение 64-битных счётчиков: задача достаточно специфическая и возможная потеря в частном случае не критична.
                                    • Compare-and-branch — просто другой стиль (как раз классического RISC). А почему "still better than ISAs which write flags to a GPR and then branch upon the resulting flags" — может, кто объяснит? только по затратам на кодирование регистра? в 32 битах места вроде хватит даже на 4*5. И ещё — современные компиляторы (LLVM как пример) как раз тяготеют к compare-and-branch на своих уровнях, и перекладывать на стиль с регистром флагов это дополнительный уровень трансляции логики. Ну и в спеке есть ещё пачка аргументов.
                                    • Умножение: есть чёткое требование по связям уровней — F и D требуют M, M не сочетается с E, в результате можно получать определённые гарантии по наличию возможностей для не-мелких применений (начиная с лаптопа) и в то же время не требовать лишнего для embedded. Хотя деление, да, можно было и отцепить (а самое странное в дополнительном требовании S*U умножения). Видимо, нашли, что воткнуть простейший делитель — дёшево по сравнению с прочими затратами.
                                    • Атомики в базе: тут явная ориентация уже на логику компиляторов. Если компилируется код под одноядерность, никакие атомики не нужны, а если под многоядерность, то начинаются активные применения всяких amoadd. Можно было бы сделать что lr и sc в варианте без атомиков присутствуют и не отличаются от load и store, но если их всё равно не будут компилировать, то зачем?
                                    • Атомики на 1 и 2 байтах: а зачем?
                                    • Знаковое расширение в регистрах: для OoO лучше любое расширение, но убрать связь с предыдущим значением. Знаковое же лучше соответствует типовой специфике программ. Тут больше жалко не этого, а отсутствия инструкций типа семейства BFM из ARM.
                                    • Нельзя в RV32 копировать double из общего регистра — а где такое нужно?

                                    Что в жалобах выглядит явно разумным:


                                    • Хинты. Вообще в современных ISA давно имеет смысл в обязательном порядке требовать 1/16-1/8 кодового пространства на действия, которые, если процессор не знает, выполнять как NOP.
                                    • Игнорирование binfloat16.
                                    • Отсутствие явного CAS. Это может быть следствием желания сократить регистр, но могли бы писать в rd признак успеха, а не старое значение.
                                    • Путаная логика "когда sc ещё валидна". Но тут все LL/SC странноваты без явного указания границ домена зависимости. Почему нет инструкции "сбросить предыдущие зависимости"?

                                    Что бы я добавил от себя:


                                    • Проблемы с загрузкой 64-битных констант. Не всегда их можно поместить и рядом с кодом (тут auipc спас бы через доп. доступ в память), нет аналога movk.
                                    • (повторюсь) аналоги семейства BFM были бы очень вкусны.
                                    • Странности выбора кодов типа: mov делается через add, а не or или xor. Это в дополнение к "почему addi, а не add?"

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

                                    Самое читаемое