Пара возможных причин:
IN
и OUT
) Ассемблерный код x86 сложен, потому что x86 - сложная архитектура. со многими функциями. Список инструкций для типичной машины MIPS умещается на листе бумаги размером с одну букву.Эквивалентный список для x86 занимает несколько страниц, а инструкции просто делают больше, поэтому вам часто требуется более подробное объяснение того, что они делают, чем может дать листинг. Например, инструкции MOVSB
требуется относительно большой блок кода C для описания того, что она делает:
if (DF == 0)
* (byte *) DI ++ = * (байт *) SI ++;
else
* (byte *) DI-- = * (byte *) SI -;
Это одна инструкция, выполняющая загрузку, сохранение и две добавляет или вычитает (управляется вводом флага), каждая из которых будет отдельными инструкциями на RISC-машине.
Хотя простота MIPS (и подобных архитектур) не обязательно делает их лучше, для обучения введению в класс ассемблера имеет смысл начать с более простого ISA . Некоторые классы сборки учат ультра-упрощенное подмножество x86, называемое y86 , которое упрощено до такой степени, что бесполезно для реального использования (например, без инструкций сдвига), или некоторые учат только базовым инструкциям x86.
РЕДАКТИРОВАТЬ: Это не должно быть bash x86! участник.У меня не было другого выбора, кроме как нанести некоторый удар, учитывая формулировку вопроса. Но за исключением (1), все это было сделано по уважительным причинам (см. Комментарии). Дизайнеры Intel не глупы - они хотели чего-то добиться с помощью своей архитектуры, и это лишь некоторые из налогов, которые им пришлось заплатить, чтобы воплотить эти вещи в жизнь.
Я не эксперт, но кажется, что Многие функции, которые не нравятся людям, могут быть причиной того, что он хорошо работает. Несколько лет назад наличие регистров (вместо стека), регистровых фреймов и т. Д. Считалось хорошим решением, позволяющим сделать архитектуру более простой для людей. Однако в настоящее время важна производительность кеша, и слова переменной длины x86 позволяют хранить в кеше больше инструкций. «Декодирование инструкций», на которое, как я полагаю, указали оппоненты, когда-то занимало половину чипа, уже не совсем так.
Я думаю, что параллелизм - один из наиболее важных факторов в настоящее время - по крайней мере, для алгоритмов, которые уже работают достаточно быстро, чтобы их можно было использовать.Выражение высокого параллелизма в программном обеспечении позволяет оборудованию амортизировать (или часто полностью скрывать) задержки памяти. Конечно, более далекое будущее архитектуры, вероятно, связано с чем-то вроде квантовых вычислений.
Я слышал от nVidia, что одна из ошибок Intel заключалась в том, что они хранили двоичные форматы близко к оборудованию. PTX CUDA выполняет некоторые быстрые вычисления использования регистров (раскрашивание графиков), поэтому nVidia может использовать регистровую машину вместо стековой, но все же имеет путь обновления, который не нарушает все старое программное обеспечение.
Помимо уже упомянутых причин:
__ cdecl
, __ stdcall
, __ fastcall
и т. Д. Язык ассемблера x86 не так уж и плох. Когда вы дойдете до машинного кода, он начинает становиться действительно уродливым. Кодирование инструкций, режимы адресации и т. Д. Намного сложнее, чем для большинства процессоров RISC. И для целей обратной совместимости встроено дополнительное развлечение - вещи, которые срабатывают только тогда, когда процессор находится в определенном состоянии.
Например, в 16-битных режимах адресация может показаться совершенно необычной; есть режим адресации для [BX + SI]
, но не для [AX + BX]
. Подобные вещи, как правило, усложняют использование регистров, так как вам нужно убедиться, что ваше значение находится в регистре, который вы можете использовать по мере необходимости.
(К счастью, 32-битный режим намного разумнее (хотя иногда сам по себе все еще немного странный - например, сегментация), а 16-битный код x86 больше не актуален за пределами загрузчиков и некоторых встроенных сред.)
Есть также остатки былых времен, когда Intel пыталась сделать x86 лучшим процессором. Инструкции длиной в пару байтов, которые выполняли задачи, которые на самом деле больше никто не выполняет, потому что они были, откровенно говоря, слишком медленными или сложными. Инструкции ENTER и LOOP , для двух примеров - обратите внимание, что код кадра стека C похож на «push ebp; mov ebp, esp», а не «enter» для большинства компиляторов.
Архитектура x86 восходит к дизайну микропроцессора 8008 и его родственников. Эти процессоры были разработаны в то время, когда память была медленной, и если бы вы могли сделать это на кристалле ЦП, он часто был намного быстрее. Однако место на кристалле ЦП также было дорогим. Эти две причины заключаются в том, что существует лишь небольшое количество регистров, которые, как правило, имеют специальное назначение, и сложный набор инструкций со всевозможными подводными камнями и ограничениями.
Другие процессоры той же эпохи (например, семейство 6502) также имеют аналогичные ограничения и особенности. Интересно, что и серия 8008, и серия 6502 предназначались как встраиваемые контроллеры. Даже тогда ожидалось, что встроенные контроллеры будут программироваться на ассемблере и во многом ориентированы на программиста на ассемблере, а не на автора компилятора. (Посмотрите на микросхему VAX, чтобы узнать, что происходит, когда вы обслуживаете компилятор.) Разработчики не ожидали, что они станут вычислительными платформами общего назначения; вот для чего были нужны вещи, подобные предшественникам архива POWER. Революция домашних компьютеров, конечно, изменила это.
Главный удар по x86, на мой взгляд, - это его происхождение от CISC - набор инструкций содержит множество неявных взаимозависимостей. Эти взаимозависимости затрудняют выполнение таких операций, как переупорядочение инструкций на микросхеме, поскольку артефакты и семантика этих взаимозависимостей должны сохраняться для каждой инструкции.
Например, большинство инструкций сложения и вычитания целых чисел x86 изменяют регистр флагов.После выполнения сложения или вычитания следующей операцией часто является просмотр регистра флагов на предмет переполнения, бит знака и т. Д. Если после этого есть еще одно добавление, очень сложно определить, безопасно ли начинать выполнение второго сложения. до того, как станет известен результат 1-го добавления.
В архитектуре RISC инструкция добавления должна указывать входные операнды и выходные регистры, и все, что касается операции, будет происходить с использованием только этих регистров. Это значительно упрощает разделение операций добавления, которые находятся рядом друг с другом, потому что нет регистра флагов bloomin, заставляющего все выстраиваться в линию и выполнять один файл.
Микросхема DEC Alpha AXP, выполненная в стиле RISC в стиле MIPS, была болезненно спартанской в доступных инструкциях, но набор инструкций был разработан таким образом, чтобы избежать неявных зависимостей регистров между инструкциями. Не было аппаратного регистра стека. Регистр аппаратно определяемых флагов отсутствовал. Даже указатель инструкции был определен ОС - если вы хотели вернуться к вызывающей стороне, вам нужно было решить, как вызывающая сторона собиралась сообщить вам, по какому адресу вернуться. Обычно это определялось соглашением о вызовах ОС. Однако на x86 это определяется аппаратным обеспечением чипа.
Так или иначе, в течение 3 или 4 поколений микросхем Alpha AXP аппаратное обеспечение превратилось из буквальной реализации спартанского набора команд с 32 регистрами int и 32 регистрами с плавающей запятой в механизм массового неупорядоченного выполнения с 80 внутренними регистрами, переименование регистров, пересылка результатов (где результат предыдущей инструкции пересылается более поздней инструкции, которая зависит от значения) и всевозможные дикие и сумасшедшие бустеры производительности. И несмотря на все эти навороты, кристалл чипа AXP все еще был значительно меньше, чем сопоставимый кристалл чипа Pentium того времени, а AXP был чертовски быстрее.
Вы не увидите такого рода всплесков повышения производительности в генеалогическом дереве x86 в основном потому, что сложность набора инструкций x86 делает многие виды оптимизации выполнения непомерно дорогими, если не невозможными. Гениальный ход Intel заключался в отказе от реализации набора инструкций x86 на оборудовании - все современные чипы x86 на самом деле являются ядрами RISC, которые в определенной степени интерпретируют инструкции x86, переводя их во внутренний микрокод, сохраняющий всю семантику исходного x86. инструкция, но допускает небольшую часть этого неупорядоченного RISC и другие оптимизации микрокода.
Я написал много ассемблера x86 и могу полностью оценить удобство его корней CISC. Но я не полностью осознавал, насколько сложна x86, пока не потратил некоторое время на написание ассемблера Alpha AXP. Я был потрясен простотой и единообразием AXP. Различия огромны и глубоки.
Я думаю, вы получите часть ответа, если когда-нибудь попытаетесь написать компилятор, ориентированный на x86, или если вы напишете эмулятор машины x86, или даже если вы попытаетесь реализовать ISA в конструкции оборудования.
Хотя я понимаю, что «x86 - это некрасиво!» аргументы, я все еще думаю забавнее писать сборку x86, чем MIPS (например) - последнее просто утомительно. Это всегда должно было быть приятным для компиляторов, а не для людей. Я не уверен, что микросхема могла бы быть более враждебной по отношению к разработчикам компиляторов, если бы попыталась ...
Самая уродливая часть для меня - это способ (в реальном режиме) работы сегментации - что любой физический адрес имеет 4096 псевдонимов сегмент: смещение. Когда в последний раз вам это было нужно ? Все было бы намного проще, если бы сегментная часть была строго старшими битами 32-битного адреса.
x86 имеет очень, очень ограниченный набор регистров общего назначения
, он продвигает очень неэффективный стиль разработки на самом низком уровне (ад CISC) вместо эффективной методологии загрузки / сохранения
Intel приняла ужасающее решение ввести явно глупую модель адресации сегмента / смещения памяти, чтобы оставаться совместимой с (уже сейчас!) устаревшей технологией
В то время, когда все переходили на 32-битную версию, x86 сдерживал массовый мир ПК, будучи скудным 16-битный (большинство из них - 8088 - даже только с 8-битными внешними путями данных, что еще страшнее!) CPU
Для меня (и я ветеран DOS, который видел каждое поколение ПК с перспектива разработчиков!) пункт 3. был худшим.
Представьте себе следующую ситуацию, которая была у нас в начале 90-х (мейнстрим!):
а) Операционная система, которая имела безумные ограничения по устаревшим причинам (640 КБ легкодоступной ОЗУ) - DOS
б) Операционная система расширение (Windows), которое могло бы делать больше с точки зрения ОЗУ, но было ограничено, когда дело касалось таких вещей, как игры и т. д., и было не самым стабильным на Земле (к счастью, позже это изменилось, но я говорю о здесь начало 90-х)
c) Большая часть программного обеспечения все еще была DOS, и нам часто приходилось создавать загрузочные диски для специального программного обеспечения, потому что там был EMM386.exe, что некоторым программам нравилось, а другим не нравилось (особенно геймеры - а я в то время был геймером AVID - знаю, о чем я здесь говорю)
г) Мы были ограничены битами MCGA 320x200x8 (хорошо, было немного еще со специальными приемами, 360x480x8 было возможно, но только без поддержки библиотеки времени выполнения), все остальное было беспорядочно и ужасно ("VESA" - смеется)
e) Но с точки зрения оборудования у нас были 32-битные машины с довольно большим количеством мегабайт карт RAM и VGA с поддержкой до 1024x768
Причина плохой ситуации?
Простое дизайнерское решение Intel. Совместимость уровня машинных инструкций (НЕ двоичного уровня!) С чем-то, что уже умирало, я думаю, это был 8085. Другие, казалось бы, не связанные проблемы (графические режимы и т. Д.) Были связаны по техническим причинам и из-за очень узкой Платформа x86 принесла с собой продуманную архитектуру.
Сегодня ситуация иная, но спросите любого разработчика ассемблера или людей, которые создают серверные части компилятора для x86. Безумно малое количество регистров общего назначения - не что иное, как ужасный убийца производительности.