Ключевые понятия, которые стоит изучить в ассемблере

StackOverflow https://stackoverflow.com/questions/146893

  •  02-07-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

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

Вы, наверное, думаете, что целочисленные операции самые быстрые.Если вы хотите найти целый квадратный корень из целого числа (т.Floor(sqrt(i))) лучше всего использовать процедуру аппроксимации только целыми числами, верно?

Неа.Математический сопроцессор (то есть на x86) имеет fsqrt инструкция.Преобразование в число с плавающей запятой, извлечение квадратного корня и повторное преобразование в целое число выполняется быстрее, чем алгоритм, использующий только целые числа.

Кроме того, есть такие вещи, как доступ к памяти, за которыми вы можете следить, но не сможете их оценить должным образом, пока не углубитесь в ассемблер.Предположим, у вас есть связанный список, и первый элемент в списке содержит переменную, к которой вам придется часто обращаться.Список переупорядочивается редко.Что ж, каждый раз, когда вам нужно получить доступ к этой переменной, вам нужно загрузить указатель на первый элемент в списке, а затем, используя его, загрузить переменную (при условии, что вы не можете хранить адрес переменной в регистре между использованиями). .Если вместо этого вы сохранили переменную вне списка, вам понадобится только одна операция загрузки.

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

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

Есть небольшие хитрости, такие как PUSH обратного адреса, а затем JMP в процедуру;когда процедура вернется, она перейдет по адресу PUSHed.Этот отход от обычного подхода к вызовам функций — еще одно из «состояний просветления».Если вам когда-либо приходилось разрабатывать язык программирования с инновационными функциями, вам следует знать о забавных вещах, на которые способно оборудование.

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

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

Но для всего этого нужен правильный ум.Если вы тот человек, который может поставить

while(x--)
{
  ...
}

с пользой, если вы изучите, что он делает, но вам будет трудно понять, что он делает самостоятельно, тогда ассемблер, вероятно, станет пустой тратой вашего времени.

Другие советы

Распределение регистров и управление ими

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

Это действительно помогло мне с кодированием на C.Я стараюсь делать все петли тугими и простыми, используя как можно меньше спагетти.

х86 это тупо

Изучение нескольких языков ассемблера позволило мне понять, насколько убогим является набор команд x86.Инструкции переменной длины?Трудно предсказать время?Неортогональные режимы адресации?Фу.

Я думаю, мир был бы лучше, если бы мы все использовали MIPS или даже ARM или PowerPC :-) Или, скорее, если бы Intel/AMD взяли свой опыт в области полупроводников и использовали его для создания многоядерных, сверхбыстрых и сверхдешевых MIPS. процессоры вместо процессоров x86 со всеми этими достоинствами.

Хорошо знать язык ассемблера, чтобы лучше понять, как компьютер работает «под капотом», и это помогает, когда вы что-то отлаживаете, и все, что может дать вам отладчик, — это листинг ассемблерного кода, который, по крайней мере, дает вам шанс выяснить, в чем может быть проблема.Однако пытаться применить низкоуровневые знания к языкам программирования высокого уровня, например, пытаться воспользоваться тем, как процессор кэширует инструкции, а затем писать шаткий высокоуровневый код, чтобы заставить компилятор создавать сверхэффективный машинный код, вероятно, является нецелесообразным. признак того, что вы пытаетесь микрооптимизировать.В большинстве случаев обычно лучше не пытаться перехитрить компилятор, если только вам не нужен прирост производительности, и в этом случае вы все равно можете записать эти биты на ассемблере.

Итак, знать ассемблер полезно для лучшего понимания того, как все работает, но полученные знания не обязательно напрямую применимы к тому, как вы пишете код на языках высокого уровня.Однако на этой ноте я обнаружил, что изучение того, как вызовы функций работают на уровне ассемблерного кода (изучение стека и связанных с ним регистров, изучение того, как параметры передаются в стек, изучение того, как работает автоматическое сохранение и т. д.), позволило добиться успеха. намного легче понять проблемы, с которыми я столкнулся в коде более высокого уровня, такие как ошибки «недостаточно места в стеке» и ошибки «недопустимого соглашения о вызовах».

Самая важная концепция — SIMD и ее творческое использование.Правильное использование SIMD может дать огромный выигрыш в производительности в самых разных приложениях: от обработки строк до манипуляций с видео и матричных вычислений.Здесь можно преодолеть 10-кратное повышение производительности по сравнению с чистым кодом C-- вот почему сборка по-прежнему полезна, помимо простой отладки.

Несколько примеров из проекта, над которым я работаю (все цифры — это количество тактовых циклов процессора Core 2):

Инверсный 8x8 H.264 DCT (преобразование частоты):

c: 1332
mmx: 187
sse2: 127

Компенсация движения цветности 8x8 (фильтр билинейной интерполяции):

c: 639
mmx: 144
sse2: 110
ssse3: 79

4 16x16 Операции суммы абсолютных разностей (поиск движения):

c: 3948
mmx: 278
sse2: 231
ssse3: 215

(да, именно так — более чем в 18 раз быстрее, чем C!)

Среднеквадратическая ошибка блока 16х16:

c: 1013
mmx: 193
sse2: 131

Вариант блока 16х16:

c: 783
mmx: 171
sse2: 106

Память, регистры, переходы, циклы, сдвиги и различные операции, которые можно выполнять на ассемблере.Я не скучаю по дням отладки программ на уроках ассемблера — они были болезненными!- но это, безусловно, дало мне хорошую основу.

Мы забываем (или, возможно, никогда не знали), что все эти модные штаны, которые мы носим сегодня (и которые я люблю!), в конце концов, сводятся ко всем этим вещам.

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

Я бы сказал, что изучение рекурсии и циклов в ассемблере научило меня многому.Это помогло мне понять основную концепцию того, как компилятор/интерпретатор языка, который я использую, помещает элементы в стек и извлекает их по мере необходимости.Я также научился использовать печально известное переполнение стека.(что по-прежнему на удивление легко выполняется на C с помощью некоторых команд get и put).

Я не думаю, что я бы использовал какие-либо концепции, которым меня научила ассемблер, кроме использования asm в повседневных ситуациях.

Я бы сказал, что режимы адресации чрезвычайно важны.

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

время

быстрое исполнение:

  • параллельная обработка
  • простые инструкции
  • таблицы поиска
  • прогнозирование ветвей, конвейеризация

быстрый и медленный доступ к хранилищу:

  • регистры
  • кэш и различные уровни кэша
  • куча и стек памяти
  • виртуальная память
  • внешний ввод-вывод

Сегодня x86 asm — это не прямой доступ к внутренностям процессора, а скорее API.Коды операций ассемблера, которые вы пишете, сами компилируются в совершенно другой набор инструкций, переставляются, переписываются, исправляются и, как правило, искажаются до неузнаваемости.

Так что изучение ассемблера не дает вам фундаментального понимания того, что происходит внутри процессора.ИМХО, важнее, чем изучение ассемблера, получить хорошее понимание того, как работает целевой процессор и иерархия памяти.

Этот серия статей довольно подробно освещает последнюю тему.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top