Почему x86 некрасиво? Почему он считается уступающим по сравнению с другими?

Недавно я читал некоторые архивы SO и встречался с утверждениями относительно архитектуры x86.

и еще много комментариев, например

Я пробовал искать, но не нашел причин. Я не считаю x86 плохой, потому что это единственная архитектура, с которой я знаком.

Может кто-то любезно дать мне основания для рассмотрения x86 уродливого/плохого/худшего по сравнению с другими.

Ответ 1

Несколько возможных причин для этого:

  • x86 является относительно старым ISA (его предшественники составляли 8086s, в конце концов)
  • x86 развивается значительно в несколько раз, но аппаратное обеспечение требуется для обеспечения обратной совместимости со старыми двоичными файлами. Например, современное оборудование x86 все еще содержит поддержку для запуска 16-битного кода изначально. Кроме того, существует несколько моделей адресации памяти, позволяющих более раннему коду взаимодействовать с одним и тем же процессором, таким как реальный режим, защищенный режим, режим виртуального 8086 и длинный режим (amd64). Это может смутить некоторых.
  • x86 - машина CISC. Долгое время это означало, что он был медленнее, чем RISC-машины, такие как MIPS или ARM, потому что команды взаимозависимости данных и флаги, что затрудняет реализацию большинства форм уровня команд parallelism, Современные реализации преобразуют инструкции x86 в RISC-подобные инструкции под названием " micro-ops" под крышками, чтобы сделать такие виды оптимизации практичными для реализации в аппаратное обеспечение.
  • В некоторых отношениях x86 не уступает, он совсем другой. Например, ввод/вывод обрабатывается как отображение памяти на подавляющем большинстве архитектур, но не на x86. (NB: Современные компьютеры x86 обычно имеют некоторую форму DMA и взаимодействуют с другим оборудованием посредством сопоставления памяти, но ISA все еще имеют инструкции ввода-вывода, такие как IN и OUT)
  • В x86 ISA имеется очень мало архитектурных регистров, которые могут заставлять программы обходить заново память чаще, чем это было бы необходимо, Дополнительные инструкции, необходимые для этого, требуют ресурсов выполнения, которые могут быть потрачены на полезную работу, хотя эффективная пересылка в хранилище поддерживает низкую задержку. Современные реализации с переименованием регистров в большой файл физического регистра могут содержать много инструкций в полете, но отсутствие архитектурных регистров по-прежнему остается значительной слабостью для 32-разрядного x86. x86-64 увеличивается с 8 до 16 целых и векторных регистров - один из самых больших факторов в 64-битном коде, который быстрее, чем 32-разрядный (наряду с более эффективным вызовом ABI), а не увеличенная ширина каждого регистра. Дальнейшее увеличение от 16 до 32 целых регистров поможет некоторым, но не таким большим. (AVX512 действительно увеличивает до 32 векторных регистров, хотя, поскольку код с плавающей запятой имеет более высокую задержку и часто нуждается в большем количестве констант.) (см. Комментарий)
  • ассемблерный код x86 является сложным, потому что x86 - сложная архитектура со многими функциями. Список инструкций для типичной машины MIPS подходит для бумаги с одной буквой. Эквивалентный список для x86 заполняет несколько страниц, а инструкции просто делают больше, поэтому вам часто требуется более подробное объяснение того, что они делают, чем может предоставить листинг. Например, команда MOVSB требует относительно большого блока кода C для описания того, что он делает:

    if (DF==0) 
      *(byte*)DI++ = *(byte*)SI++; 
    else 
      *(byte*)DI-- = *(byte*)SI--;
    

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

    В то время как простота MIPS (и аналогичных архитектур) не обязательно делает их превосходными, для обучения введению в класс ассемблера имеет смысл начать с более простого ISA. Некоторые классы сборки обучают ультра-упрощенному подмножеству x86, называемому y86, который упрощается за пределами того, что он не полезен для реального использования ( например, инструкции о сдвиге), или некоторые из них учат только основным инструкциям x86.

  • x86 использует коды операций переменной длины, которые добавляют аппаратную сложность в отношении анализа инструкций. В современную эпоху эта стоимость становится все менее малой, поскольку процессоры становятся все более ограниченными пропускной способностью памяти, чем при необработанном вычислении, но многие статьи и отношения "x86 bashing" исходят из эпохи, когда эта стоимость была сравнительно намного больше. Обновление 2016: Anandtech опубликовала обсуждение относительно размеров опкодов под x64 и AArch64.

EDIT: это не предполагается bash x86! вечеринка. У меня не было другого выбора, кроме как сделать некоторые избиения, учитывая то, как сформулирован вопрос. Но, за исключением (1), все это было сделано по уважительным причинам (см. Комментарии). Дизайнеры Intel не глупы - они хотели добиться чего-то с их архитектурой, и это некоторые из налогов, которые они должны были заплатить, чтобы сделать эти вещи реальностью.

Ответ 2

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

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

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

Чип DEC Alpha AXP, дизайн RISC в стиле MIPS, был болезненным спартанцем в доступных инструкциях, но набор инструкций был разработан, чтобы избежать зависимостей между зависимыми регистрами между инструкциями. Не было аппаратного регистра стека. Не было аппаратно определенных флагов. Даже указатель инструкции был определен ОС - если вы хотите вернуться к вызывающему абоненту, вам нужно было выяснить, как вызывающий абонент узнает, к какому адресу вернуться. Обычно это определялось конвенцией вызова ОС. Однако на x86 он определяется аппаратным обеспечением чипа.

В любом случае, более 3 или 4 поколения процессоров Alpha AXP, аппаратное обеспечение перешло от литеральной реализации спартанского набора команд с 32-ю регистрами и 32 регистрами с плавающей запятой для механизма массового выхода из строя с 80 внутренними регистрами, переименование регистра, перенаправление результатов (когда результат предыдущей команды пересылается на более позднюю инструкцию, которая зависит от значения) и всевозможные дикие и сумасшедшие ускорители производительности. И со всеми этими колокольчиками кристалл AXP был значительно меньше, чем сопоставимый чип Pentium того времени, и AXP был намного быстрее.

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

Я написал много ассемблера x86 и могу полностью оценить удобство своих корней CISC. Но я не совсем понял, насколько сложна x86, пока я не потратил некоторое время на сборку ассемблера Alpha AXP. Я был близок к простоте и единообразию AXP. Различия огромны и глубоки.

Ответ 3

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

Другие процессоры той же эпохи (например, семейства 6502) также имеют схожие ограничения и причуды. Интересно отметить, что серия 8008 и серия 6502 были предназначены как встроенные контроллеры. Даже тогда, как предполагалось, встроенные контроллеры были запрограммированы на ассемблере и во многом удовлетворены программистом сборки, а не компилятором. (Посмотрите на чип VAX, что происходит, когда вы пишете компилятор.) Дизайнеры не ожидали, что они станут платформами общего назначения; что то, что было похоже на предшественников ЭНЕРГЕТИЧЕСКОЙ ПРОБЛЕМЫ. Разумеется, революция на домашнем компьютере изменилась.

Ответ 4

У меня есть несколько дополнительных аспектов:

Рассмотрим операцию "a = b/c" x86 будет реализовывать это как

  mov eax,b
  xor edx,edx
  div dword ptr c
  mov a,eax

В качестве дополнительного бонуса команды div edx будет содержать остаток.

Для RISC-процессора потребуется сначала загрузить адреса b и c, загрузить b и c из памяти в регистры, выполнить разделение и загрузить адрес a, а затем сохранить результат. Dst, синтаксис src:

  mov r5,addr b
  mov r5,[r5]
  mov r6,addr c
  mov r6,[r6]
  div r7,r5,r6
  mov r5,addr a
  mov [r5],r7

Здесь обычно не будет остатка.

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

Плюсы и минусы:

Инструкции RISC могут быть смешаны с окружающим кодом для улучшения планирования команд, это меньше возможностей для x86, который вместо этого выполняет эту работу (более или менее хорошо в зависимости от последовательности) внутри самого ЦП. Последовательность RISC выше обычно составляет 28 байтов (7 инструкций ширины 32 бит /4 байта каждый) в 32-битной архитектуре. Это приведет к тому, что память вне кристалла будет работать больше при извлечении инструкций (семь наборов). Более плотная последовательность x86 содержит меньше инструкций, и хотя их ширина варьируется, вы, вероятно, тоже смотрите на 4 байта/инструкцию. Даже если у вас есть кэши команд, чтобы ускорить это, семь выборок означают, что у вас будет дефицит из трех в другом месте, чтобы компенсировать его по сравнению с x86.

Архитектура x86 с меньшим количеством регистров для сохранения/восстановления означает, что она, вероятно, будет выполнять поточные коммутаторы и обрабатывать прерывания быстрее, чем RISC. Для большего количества регистров для сохранения и восстановления требуется более временное пространство стека RAM для выполнения прерываний и более постоянного пространства стека для хранения состояний потоков. Эти аспекты должны сделать x86 лучшим кандидатом для запуска чистой RTOS.

В более личном примечании мне сложнее написать сборку RISC, чем x86. Я решаю это, написав процедуру RISC в C, компилируя и изменяя сгенерированный код. Это более эффективно с точки зрения производства кода и, вероятно, менее эффективно с точки зрения исполнения. Все эти 32 регистра должны отслеживаться. С x86 это наоборот: 6-8 регистров с "настоящими" именами делают проблему более управляемой и внушает большую уверенность в том, что созданный код будет работать, как ожидалось.

Гадкий? Это в глазах смотрящего. Я предпочитаю "разные".

Ответ 5

Я думаю, что этот вопрос имеет ложное предположение. Это в основном просто одержимые RISC ученые, которые называют x86 уродливыми. В действительности ISA x86 может выполнять в одной операции команды, которая будет принимать 5-6 инструкций для RISC ISAs. Вентиляторы RISC могут противостоять тому, что современные процессоры x86 разбивают эти "сложные" инструкции на микропы; Однако:

  • Во многих случаях это только частично истинно или не совсем верно. Наиболее полезными "сложными" инструкциями в x86 являются такие вещи, как mov %eax, 0x1c(%esp,%edi,4) i.e режимы адресации, и они не разбиты.
  • Чаще всего на современных машинах важнее не количество потраченных циклов (поскольку большинство задач не привязаны к cpu), а влияние кэша команд на код. 5-6 фиксированных (обычно 32-битных) инструкций будут влиять на кеш-память более чем на одну сложную инструкцию, которая редко превышает 5 байтов.

x86 действительно поглотил все хорошие аспекты RISC около 10-15 лет назад, а остальные качества RISC (фактически определяющие - минимальный набор команд) вредны и нежелательны.

Помимо стоимости и сложности изготовления процессоров и их энергетических требований, x86 является лучшим ISA. Любой, кто говорит вам иначе, позволяет идеологии или повестке дня мешать их рассуждениям.

С другой стороны, если вы нацеливаете встроенные устройства, где стоит стоимость процессора, или встроенные/мобильные устройства, где потребление энергии является главной проблемой, возможно, ARM или MIPS имеют больше смысла. Имейте в виду, что вам все равно придется иметь дело с дополнительными барабанами и бинарными размерами, необходимыми для обработки кода, который в 3-4 раза больше, и вы не сможете приблизиться к производительности. Неважно, зависит ли это от того, на чем вы будете работать.

Ответ 6

x86 язык ассемблера не так уж плох. Это когда вы добираетесь до машинного кода, который начинает становиться действительно уродливым. Кодировки команд, режимы адресации и т.д. Намного сложнее, чем для большинства RISC-процессоров. И там дополнительное удовольствие встроено для обратной совместимости - материал, который срабатывает только тогда, когда процессор находится в определенном состоянии.

В 16-битных режимах, например, адресация может казаться совершенно странной; существует режим адресации для [BX+SI], но не один для [AX+BX]. Такие вещи, как правило, усложняют использование регистров, поскольку вам необходимо обеспечить свою ценность в регистре, который вы можете использовать, как вам нужно.

(К счастью, 32-битный режим намного более сдержанный (хотя иногда немного странный сам по себе - сегментация, например), а 16-разрядный код x86 в значительной степени не имеет значения больше вне загрузчиков и некоторых встроенных сред.)

Там также остатки старых лет, когда Intel пыталась сделать x86 окончательным процессором. Инструкции за пару байтов, которые выполняли задачи, которые никто на самом деле не делает, потому что они откровенно слишком запутывают медленно или сложно. Команды ENTER и LOOP для двух примеров - обратите внимание, что код кадра стека C похож на "push ebp; mov ebp, esp", а не "enter" для большинства компиляторов.

Ответ 7

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

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

Я слышал от nVidia, что одна из ошибок Intel заключалась в том, что они сохраняли двоичные форматы близко к оборудованию. CUDA PTX выполняет некоторые быстрые вычисления использования регистров (раскраска графа), поэтому nVidia может использовать машину регистрации вместо стековой машины, но все же имеет путь обновления, который не разбивает все старое программное обеспечение.

Ответ 8

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

Хотя я понимаю, что "x86 уродлив!" аргументы, я все еще думаю, что более интересно писать сборку x86, чем MIPS (например) - последнее просто утомительно. Это всегда предназначалось для компиляторов, а не для людей. Я не уверен, что чип может быть более враждебным для компиляторов, если он попытался...

Самая уродливая часть для меня - это способ (в реальном режиме) сегментации - что любой физический адрес имеет 4096 сегментов: смещение псевдонимов. Когда тебе это понадобилось? Все было бы намного проще, если бы сегментная часть была строго битами более высокого порядка 32-битного адреса.

Ответ 9

Помимо причин, о которых люди уже упоминали:

Ответ 10

  • x86 имеет очень, очень ограниченный набор регистров общего назначения

  • это способствует очень неэффективному стилю разработки на самом низком уровне (CISC hell) вместо эффективной методологии загрузки/хранения

  • Корпорация Intel приняла ужасное решение о внедрении явно тупой модели адресации/смещения памяти, чтобы оставаться совместимой с устаревшей технологией (в это время уже!)

  • В то время, когда все шли 32 бит, x86 удерживал основной мир ПК, будучи скудным 16-битным (большинство из них - 8088 - даже с 8-битными внешними маршрутами данных, что даже страшнее!) CPU


Для меня (и я - ветеран DOS, который видел каждое поколение ПК с точки зрения разработчиков!). Самое худшее - точка 3.

Представьте себе следующую ситуацию, сложившуюся в начале 90-х (mainstream!):

a) Операционная система, которая имела безумные ограничения по унаследованным причинам (640 КБ легкодоступной ОЗУ) - DOS

b) Расширение операционной системы (Windows), которое могло бы делать больше с точки зрения ОЗУ, но было ограничено, когда дело доходило до таких вещей, как игры и т.д.... и не было самой стабильной на Земле (к счастью, это изменилось позже, но я говорю о начале 90-х здесь)

c) Большинство программных продуктов по-прежнему были DOS, и нам приходилось часто создавать загрузочные диски для специального программного обеспечения, потому что это был EMM386.exe, который понравился некоторым программам, другие ненавидели (особенно геймеры - и в то время я был игроком AVID) знайте, о чем я говорю)

d) Мы были ограничены MCGA 320x200x8 бит (хорошо, было немного больше со специальными трюками, возможно 360x480x8, но только без поддержки библиотеки времени исполнения), все остальное было грязным и ужасным ( "VESA" - lol)

e) Но с точки зрения аппаратного обеспечения у нас были 32-битные машины с довольно большим количеством мегабайт оперативной памяти и VGA-карт с поддержкой до 1024x768

Причина этой плохой ситуации?

Простое дизайнерское решение от Intel. Уровень машинной инструкции (не двоичный уровень!) Совместим с тем, что уже умирало, я думаю, что это был 8085. Другие, казалось бы, несвязанные проблемы (графические режимы и т.д.) Были связаны по техническим причинам и из-за очень узкого архитектуру x86, принесенную с собой.

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