Оптимизация программ на ассемблере (часть 2)


Содержание

Способы увеличения быстродействия программ

Современные ЭВМ обладают очень большой мощностью. Скорость работы процессора (ЦП) современных ЭВМ измеряется гигагерцами, объём оперативной памяти гигабайтами, а современные интерфейсы устройств обеспечивают скорость обмена данными порядка, как минимум, нескольких сотен мегабайт в секунду [1]. Производительность, которая ещё несколько лет назад казалась «сказочной» в настоящее время стала нормой жизни.

Однако параллельно росту мощности ЭВМ увеличивается и ресурсоёмкость приложений. У приложений совершенствуется функционал, интерфейс, возрастает объём обрабатываемых данных и как следствие системные требования. Поэтому вопрос об увеличении быстродействия приложений не теряет своей актуальности.

Общие вопросы быстродействия программ.

Быстродействие программ (ПО) зависит от многих факторов, но основными из них являются два:

  • Соотношение между реальными системными требованиями ПО и существующей аппаратной конфигурацией ЭВМ;
  • Алгоритмы работы ПО.

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

  1. Увеличивается производительность hardware, а вовсе не быстродействие ПО;
  2. Производительность hardware ограничена возможностями существующих в данный момент элементной базы и инженерных решений в данной области;
  3. Большие финансовые затраты на модернизацию и настройку по причине высокой стоимости комплектующих ЭВМ и услуг специалистов требуемой квалификации.

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

  1. Обеспечить работу нового ПО на уже существующем оборудовании;
  2. Разработать масштабируемое ПО;
  3. Значительно уменьшить финансовые и трудовые затраты при внедрении.

Вместе с тем и у этого пути имеется ряд недостатков:

  1. Значительно усложняется процесс разработки ПО, так как более «быстрые» алгоритмы сложнее более «медленных» (на пример алгоритм бинарного поиска сложнее, чем алгоритм линейного поиска) [2];
  2. Реализация более сложных алгоритмов, как правило, требует привлечения специалистов более высокой квалификации;
  3. В случае работы с большими объёмами данных или выполнении задач требующих больших и сложных вычислений, ресурсоёмкость ПО всё равно остаются достаточно высокой. Несмотря, на какие либо способы увеличения быстродействия.

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

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

Программная инженерия предоставляет несколько способов увеличения быстродействия программ. Рассмотрим их на примере языков программирования Delphi и Assembler.

Увеличение быстродействия программ.

Как было показано в предыдущем параграфе, можно увеличить быстродействие ПО соответствующим образом реализовав его алгоритмы. Количественным показателем быстродействия алгоритма (а, следовательно, и ПО) является время его выполнения, измеренное по специальной методике, так называемого профилирования [2]. Таким образом, в общем случае выбор наиболее «быстрых» алгоритмов сводится к измерению времени их выполнения и сравнении полученных результатов между собой. Такой способ анализа быстродействия является наиболее объективным [2]. На протяжении многих лет программистами был накоплен большой опыт профилирования, который позволяет сделать определённые выводы относительно возможности оптимизации быстродействия ПО ещё на стадии написания.

Эти выводы были обобщены и представлены в виде определённых рекомендаций [2, 4]. Если программист будет следовать данным рекомендациям, то написанная программа вероятнее всего будет обладать большим быстродействием, чем в случае их игнорирования. Однако следует ещё раз подчеркнуть, что достоверные сведения о быстродействии может дать только профилирование. Это обусловлено тем, что быстродействие алгоритма определяет в первую очередь его конкретная реализация. Кроме того необходимо ещё раз отметить, что в отношении увеличения быстродействия ПО программная инженерия не всесильна (см. предыдущий параграф).

В чём же состоят выше упомянутые рекомендации? Их краткое содержание применительно к языку программирования Delphi приведено ниже.

  1. При написании кода программ рекомендуется избегать процедур, состоящих из сотен строк. Практически всегда в них можно выделить блоки, которые лучше оформить в виде отдельной процедуры. Возможно, позже вы ей даже воспользуетесь где-то в другом месте. Не говоря уже о том, что это повышает понимание программы и вами, и другими программистами. К тому же так проще искать «узкие» места в программе.
  2. Использование оператора case (switch) вместо многократных if… then… else (if… else). Во втором варианте компилятор будет выполнять проверку условия столько раз, сколько у вас вариантов. В первом проверка выполняется лишь однажды.
  3. Некоторые действия могут быть довольно продолжительными, поэтому рекомендуется выносить за рамки цикла всё, что можно выполнить вне его, чтобы избежать большого числа повторений внутри цикла.
  4. В циклах типа for нужно стараться, чтобы значение счетчика уменьшалось до нуля, а не наоборот — начиналось с нуля. Это связано с особенностями процессора. Сравнение с нулём выполняется гораздо быстрее, чем с другим числом.
  5. Пользоваться типом Variant только при необходимости. Операции над этим типом сложнее, чем, например, над Integer или String.
  6. Не злоупотреблять «программированием на компонентах». В частности не использовать компонент TTreeView для хранения древовидных структур данных — он работает очень медленно и предназначен только для визуального отображения. В случае работы со структурами данных лучше использовать алгоритмы, созданные самостоятельно на основе фундаментальных.
  7. Сохранение и загрузка свойств компонентов с помощью методов ReadComponent и WriteComponent работает довольно медленно, поэтому по возможности рекомендуется сохранять и восстанавливать состояние программы между сеансами при помощи других способов.
  8. Заменить простой в реализации алгоритм на более сложный, но с большим быстродействием. Например, если заранее известно, что в списке для поиска будет много элементов, лучше его отсортировать и применять бинарный поиск вместо линейного.
  9. В критических с точки зрения быстродействия местах программы делать вставки на ассемблере. Команды ассемблера напрямую транслируются в машинный код. Таким образом, в отличие от высокоуровневых языков при компиляции отсутствует проблема синхронизации и ряд других негативных обстоятельств.

Для других языков программирования вышеприведённый список может несколько отличаться, в частности отсутствием поддержки ассемблера и как следствие возможности оптимизации с его помощью (Java, Visual C# и др.).

Особо следует отметить, что рекомендации 3 и 4 применяются не только для языков высокого уровня, но и для ассемблера. Помимо вышеуказанных для увеличения быстродействия программ написанных на ассемблере, в том числе и вставок, существуют следующие рекомендации [3]:

  1. Замещение универсальных инструкций на учитывающие конкретную ситуацию, например, замена умножения на степень двойки на команды сдвига (отказ от универсальности).
  2. Уменьшение количества передач управления в программе: за счет преобразования подпрограмм в макрокоманды для непосредственного включения в машинный код; за счет преобразования условных переходов так, чтобы условие перехода оказывалось истинным значительно реже, чем условие для его отсутствия; перемещение условий общего характера к началу разветвленной последовательности переходов; преобразование вызовов, сразу за которыми следует возврат в программу, в переходы («сращивание хвостов» и «устранение рекурсивных хвостов») и т.д.
  3. Максимальное использование всех доступных регистров за счет хранения в них рабочих значений всякий раз, когда это возможно, чтобы минимизировать число обращений к памяти, упаковка множественных значений или флагов в регистры и устранение излишних продвижений стека (особенно на входах и выходах подпрограмм).
  4. Использование специфических для данного процессора инструкций, например, инструкции засылки в стек непосредственного значения, которая имеется в процессоре 80286 и более поздних. Другие примеры – двухсловные строковые инструкции, команды перемножения 32-разрядных чисел, деление 64-разрядного на 32-разрядное число и умножение на непосредственное значение, которые реализованы в процессорах 80386 и 80486. Программа должна, разумеется, вначале определить, с каким типом процессора она работает!

Заключение.

Методы оптимизации быстродействия, рассмотренные в этой статье, были выработаны и проверены не одним поколением программистов и уже стали классическими. В то же время информационные технологии, в частности технологии программирования, постоянно развиваются. Появляются новые технологии, старые – модернизируются или уходят в прошлое. Растёт производительность аппаратной части ЭВМ и параллельно с этим растёт сложность и ресурсоёмкость выполняемых ими задач.

Поэтому вопрос оптимизации быстродействия сейчас актуален также как и много лет назад. Более того он будет также актуален и в будущем.

Список литературы и источников.

  1. Быстро и легко. Сборка, диагностика, оптимизация и апгрейд современного компьютера/ под ред. Печникова В.Н. Учебное пособие – М.: Лучшие книги, 2005;
  2. Бакнелл Д. Фундаментальные алгоритмы и структуры данных в Delphi. М.: ООО «ДиаСофтЮП»; СПб.: Питер, 2006;
  3. Дункан Р. Оптимизация программ на ассемблере./ Дункан Р.//PC Magazine/Russian Edition №1/1992;
  4. Список пропускных способностей интерфейсов передачи данных;
  5. Гапанович А. Как сделать свою программу быстрой./Компьютерра Online.

42. Оптимизация по быстродействию в Ассемблер

42. Оптимизация по быстродействию в Ассемблер

Приведем некоторые из самых общих процедур этой категории.

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

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

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

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

4. Применение специфических для этого процессора инструкций, например, инструкции засылки в стек прямого значения или умножения числа на непосредственный операнд, имеющийся в процессорах 80186, 80188, 80286, 80386 и 80486. Также примером могут быть двухсловные строковые инструкции, команды перемножения 32-разрядных чисел и деления 64-разрядного на 32-разрядное число, которые проводятся в процессорах 80386 и 80486. Программа должна, конечно, первоначально определять, с каким типом процессора она работает.

В процессорах 80 x 86, но не 80 x 88, возможно, удастся повысить скорость действия программы на несколько процентов в результате выравнивания расположения данных и меток, на которые осуществляется передача управления, относительно определенных границ.

Процессоры 8088 и 80188 имеют 8-разрядную шину, и для них не имеет значения, на какую границу выровнены данные, поэтому выравнивание можно не применять или установить на границу байта (1 байт, 8 бит); процессоры 8086, 80186 и 80286 обладают 16-разрядной шиной, и им проще действовать с данными, выровненными на границу слова (2 байта, 16 бит); процессор 80386, для которого свойственна 32-разрядная шина, использует выравнивание на границу двойного слова (4 байта, 32 бита); из-за особенностей своей внутренней кэш-памяти процессору 80486, тоже с 32-разрядной шиной, проще работать, если осуществляется выравнивание на границу параграфа (16 байт, 96 бит).

Оптимизация программного кода

Зачем же нужна оптимизация и откуда она взялась? С первых дней развития эры вычислительной техники возник вопрос экономии места и увеличения производительности программ. Программистам приходилось создавать сложные дееспособные программы, которые смогли бы работать при очень низком быстродействии процессоров, использовать считанные килобайты оперативной памяти и места на диске. Поэтому все разработчики ПО были заинтересованы в максимальном быстродействии при минимальном размере кода.

Сегодня эти мощности вызывают улыбку. Но традиции оптимизации кода сохранились. Как известно, сколько ни наращивай размер диска и объем ОЗУ, все равно будет мало. Потому написанные «неряшливо» приложения, медленные и ресурсоемкие, проигрывают конкурентную борьбу аналогам, даже если они красивы и удобны.

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

Основные принципы оптимизации

Оптимизация стоит на трех «китах» — естественность, производительность, затраченное время. Давайте разберемся подробнее, что они означают.

  1. Естественность. Код должен быть аккуратным, модульным и легко читабельным. Каждый модуль должен естественно встраиваться в программу. Код должен легко поддаваться редактированию, интегрированию или удалению отдельных функций или возможности без необходимости вносить серьезные изменения в другие части программы.
  2. Производительность. В результате оптимизации вы должны получить прирост производительности программного продукта. Как правило, удачно оптимизированная программа увеличивает быстродействие минимум на 20-30% в сравнение с исходным вариантом.
  3. Время. Оптимизация и последующая отладка должны занимать небольшой период времени. Оптимальными считаются сроки, не превышающие 10 – 15 % времени, затраченного на написание самого программного продукта. Иначе это будет нерентабельно.

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

Стоит ли применять Ассемблер

Многие разработчики искренне считают, что критические секции (некоторые называют их «узкими» местами программы) кода удобнее писать на ассемблере, так как он обеспечивает самый быстрый доступ к данным и устройствам.

Многочисленные сравнения результатов оптимизации кода на языке высокого уровня и применения ассемблерных вставок показывают, что в первом случае после компиляции программа будет работать чуть медленнее, чем при использовании ассемблера. Обычно эти цифры измеряются от 2% до 7%. Максимум – разница составляет 20%. Стоит ли ради получения столь малого эффекта тратить время и силы на написание ассемблерной версии? На наш взгляд лучше уделить больше внимания работе с кодом, который написан на ЯП высокого уровня, и оптимизировать работу алгоритма.

Как правильно оптимизировать

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

Начало оптимизации

Первое, что необходимо сделать, это выявить «узкие места» программы. Нет смысла трогать тот кусок программы, где и без вас все работает прекрасно. Здесь вы вряд ли что-то выиграете при оптимизации. В первую очередь, стоит обратить внимание на блоки кода, которые регулярно или часто повторяются в процессе работы – циклы и подпрограммы.

Пример: Если оптимизировать работу цикла хотя бы на 2% за одну итерацию, а число его повторов будет 1000 раз, в итоге мы получаем: 2% × 1000 = 2000%, вполне ощутимый результат при работе кода.

Участки кода, которые не оптимизируются

Не стоит трогать единичные операнды, поскольку работают они крайне редко и толку в их модификации нет никакого. Они отработают один раз, и больше к этому коду обращений не будет. Но при условии, что при внесении изменений вы добьетесь увеличения производительности более чем на 10%, это не лишено смысла. Здесь все зависит от вашего здравого смысла и опыта.

Также вы не сумеете добиться достойных результатов в случае обращения к внешним устройствам и другим программным системам. До и после таких фрагментов можно что-то ускорить. Но там, где задержка может возникать по причине взаимодействия с внешними данными, лучше предусмотрите заглушку типа «Подождите, операция может занять несколько минут».

Еще раз об ассемблере

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

Оптимизировать или нет?

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

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

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

Методы оптимизации программ

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

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

Пишите аккуратный код. Не забывайте о комментариях. Так вы поможете и себе, и другим разработчикам, понять, что в программе нужно, а что – уже не актуально. Эти общие советы помогают и при отладке, и при поиске багов. В общем, не будьте неряшливым «говнокодером», и ваши программы будут работать быстро и без проблем.

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

Если все равно что-то не работает или «тормозит», придется использовать профилировщики отладочного вывода, в том числе, с учетом записи логов всех запросов SQL, если программа работает с базами данных. В случае поиска вслепую вы потратите много времени и не факт, что сможете добиться положительных результатов.

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

Настройка окружения

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

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

Также обратите внимание на саму операционную систему и мощность «железа». Может быть они вообще не предназначены для работы программного продукта, который вы пытаетесь запустить и ускорить? А, может, устарели и потому «тормозят»?

Все это не относится напрямую к оптимизации программы, но проверить нужно до начала работы с кодом. Просто потому, что такие «досадные мелочи» нередко оказываются ключевой проблемой, а код – вообще не причем. И не стоит снисходительно улыбаться. Проверять окружение забывают даже опытные разработчики.

Избавляемся от ненужного функционала

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

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

Мемоизация

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


Работает это очень просто. Перед тем, как функция будет выполняться, проверяется условие – исполнялась ли она ранее. По итогам можно получить два варианта:

  • функция вызвана в первый раз, тогда она выполняется, а результат сохраняется;
  • модуль уже работал, можно использовать сохраненный результат.

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

Кеширование

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

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

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

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

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

Илон Маск рекомендует:  Faq system tray а как удалить иконку с tray

«Ленивые» вычисления

Ленивые (Lazy evaluation) или отложенные вычисления – стратегия, которую применяют в некоторых системах счисления. Суть метода заключается в том, что все расчеты откладываются до тех пор, пока не будет затребован их результат.

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

Метод приближения

Приближение или аппроксимаация (от лат. proxima — ближайшая или приближе́ние) – метод замены строгого алгоритма на наиболее подходящие приближенные значения, что влечет за собой определенную потерю точности вычислений. Снижение точности экономит память и повышает скорость. Для того чтобы не использовать длинную арифметику, можно воспользоваться обычными float’ами. Но пользоваться таким методом нужно крайне осторожно, не всегда снижение точности допустимо.

Использование сторонних языков

Иногда написанная программа может медленно работать из-за того, что много времени занимает проверка описанных типов, что занимает дополнительное время. Чтобы избежать этого эффекта, можно применять фрагменты кода или модули, написанные на других языках. Но делать это нужно крайне осторожно. Все эти «лишние» проверки защищают вас от багов и «дыр» в безопасности, связанных, в том числе, с буферизацией. Потому хорошо подумайте, действительно ли экономия времени столь существенна? И если придете к выводу, что здесь это – лучшее решение, обязательно проведите особо внимательное тестирование.

Кроме того, если начать использовать в коде фрагменты других языков, это может вызвать эффект «зоопарка», что сильно снижает читабельность программы. Также имейте в виду, что метод может попросту не сработать или даже критически навредить всей программе.

Существует еще очень много методов оптимизации, как общеизвестных, так и личных находок программистов. Мы постарались рассказать, что такое оптимизация, и познакомить вас с самыми популярными методами. А как вы ускоряете работу своих приложений? Делитесь в комментариях.

Погружение в assembler. Полный курс по программированию на асме от ][

Содержание статьи

Это первая (вступительная) статья курса. Курс рассчитан на тех, кто в целом знаком с высокоуровневым программированием и только приступает к изучению ассемблера.

Но что такое программирование само по себе по своей сути, вне зависимости от какого-либо языка? Разнообразие ответов поражает. Наиболее часто можно услышать такое определение: программирование — это составление инструкций или команд для последовательного исполнения их машиной с целью решить ту или иную задачу. Такой ответ вполне справедлив, но, на мой взгляд, не отражает всей полноты, как если бы мы назвали литературу составлением из слов предложений для последовательного прочтения их читателем. Я склонен полагать, что программирование ближе к творчеству, к искусству. Как любой вид искусства — выражение творческой мысли, идеи, программирование представляет собой отражение человеческой мысли. Мысль же бывает и гениальная, и совершенно посредственная.

Но, каким бы видом программирования мы ни занимались, успех зависит от практических навыков вкупе со знанием фундаментальных основ и теории. Теория и практика, изучение и труд — вот краеугольные камни, на которых основывается успех.

В последнее время ассемблер незаслуженно находится в тени других языков. Обусловлено это глобальной коммерциализацией, направленной на то, чтобы в максимально короткие сроки получить как можно большую прибыль от продукта. Иными словами, массовость взяла верх над элитарностью. А ассемблер, по моему мнению, ближе к последнему. Гораздо выгоднее в сравнительно небольшие сроки поднатаскать ученика в таких, например, языках, как С++, С#, PHP, Java, JavaScript, Python, чтобы он был более-менее способен создавать ширпотребный софт, не задаваясь вопросами, зачем и почему он так делает, чем выпустить хорошего специалиста по ассемблеру. Примером тому служит обширнейший рынок всевозможных курсов по программированию на любом языке, за исключением ассемблера. Та же тенденция прослеживается как в преподавании в вузах, так и в учебной литературе. В обоих случаях вплоть до сегодняшнего дня большая часть материала базируется на ранних процессорах серии 8086, на так называемом «реальном» 16-битном режиме работы, операционной среде MS-DOS! Возможно, что одна из причин в том, что, с одной стороны, с появлением компьютеров IBM PC преподавателям пришлось перейти именно на эту платформу из-за недоступности других. А с другой стороны, по мере развития линейки 80х86 возможность запуска программ в режиме DOS сохранялась, что позволяло сэкономить деньги на приобретение новых учебных компьютеров и составление учебников для изучения архитектуры новых процессоров. Однако сейчас такой выбор платформы для изучения совершенно неприемлем. MS-DOS как среда выполнения программ безнадежно устарела уже к середине девяностых годов, а с переходом к 32-битным процессорам, начиная с процессора 80386, сама система команд стала намного более логичной. Так что бессмысленно тратить время на изучение и объяснение странностей архитектуры реального режима, которые заведомо никогда уже не появятся ни на одном процессоре.

Что касается выбора операционной среды для изучения ассемблера, то, если говорить о 32-битной системе команд, выбор сравнительно невелик. Это либо операционные системы Windows, либо представители семейства UNIX.

Также следует сказать несколько слов о том, какой именно ассемблер выбрать для той или другой операционной среды. Как известно, для работы с процессорами х86 используются два типа синтаксиса ассемблера — это синтаксис AT&T и синтаксис Intel. Эти синтаксисы представляют одни и те же команды совершенно по-разному. Например, команда в синтаксисе Intel выглядит так:

В синтаксисе же AT&T уже будет иной вид:

В среде ОС UNIX более популярен синтаксис типа AT&T, однако учебных пособий по нему нет, он описывается исключительно в справочной и технической литературе. Поэтому логично выбрать ассемблер на основе синтаксиса Intel. Для UNIX-систем есть два основных ассемблера — это NASM (Netwide Assembler) и FASM (Flat Assembler). Для линейки Windows популярностью пользуются FASM и MASM (Macro Assembler) от фирмы Microsoft, и также существовал еще TASM (Turbo Assembler) фирмы Borland, которая уже довольно давно отказалась от поддержки собственного детища.

В данном цикле статей изучение будем вести в среде Windows на основе языка ассемблера MASM (просто потому, что он мне нравится больше). Многие авторы на начальном этапе изучения ассемблера вписывают его в оболочку языка си, исходя из тех соображений, что перейти к практическим примерам в операционной среде якобы довольно трудно: нужно знать и основы программирования в ней, и команды процессора. Однако и такой подход требует хоть мало-мальских начатков знаний в языке си. Данный же цикл статей от самого своего начала будет сосредоточен только на самом ассемблере, не смущая читателя ничем иным, ему непонятным, хотя в дальнейшем и будет прослеживаться связь с другими языками.

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

Что такое ассемблер?

Само слово ассемблер (assembler) переводится с английского как «сборщик». На самом деле так называется программа-транслятор, принимающая на входе текст, содержащий условные обозначения машинных команд, удобные для человека, и переводящая эти обозначения в последовательность соответствующих кодов машинных команд, понятных процессору. В отличие от машинных команд, их условные обозначения, называемые также мнемониками, запомнить сравнительно легко, так как они представляют собой сокращения от английских слов. В дальнейшем мы будем для простоты именовать мнемоники ассемблерными командами. Язык условных обозначений и называется языком ассемблера.

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

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

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

Синтаксис

Общепринятого стандарта для синтаксиса языков ассемблера не существует. Однако большинство разработчиков языков ассемблера придерживаются общих традиционных подходов. Основные такие стандарты — Intel-синтаксис и AT&T-синтаксис.

Общий формат записи инструкций одинаков для обоих стандартов:

Опкод — это и есть собственно ассемблерная команда, мнемоника инструкции процессору. К ней могут быть добавлены префиксы (например, повторения, изменения типа адресации). В качестве операндов могут выступать константы, названия регистров, адреса в оперативной памяти и так далее. Различия между стандартами Intel и AT&T касаются в основном порядка перечисления операндов и их синтаксиса при разных методах адресации.

Используемые команды обычно одинаковы для всех процессоров одной архитектуры или семейства архитектур (среди широко известных — команды процессоров и контроллеров Motorola, ARM, x86). Они описываются в спецификации процессоров.

Например, процессор Zilog Z80 наследовал систему команд Intel i8080, расширил ее и поменял некоторые команды (и обозначения регистров) на свой лад. Например, сменил Intel-команду mov на ld. Процессоры Motorola Fireball наследовали систему команд Z80, несколько ее урезав. Вместе с тем Motorola официально вернулась к Intel-командам, и в данный момент половина ассемблеров для Fireball работает с Intel-командами, а половина — с командами Zilog.

Директивы

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

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

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Разработка программы на Ассемблере (стр. 2 из 11)

9. OFFSET — оператор получения смещения адреса относительно начала сегмента (то есть количества байтов от начала сегмента до идентификатора адреса).

mov bx, OFFSET table

ДИРЕКТИВЫ (ПСЕВДООПЕРАТОРЫ)

1. : — определяет близкую метку (в пределах сегмента).

jmp lbl . .

lbl: .

2 . = — присваивает символическому имени значение выражения.

3. .CODE — определяет начало кодового сегмента, то есть сегмента, где располагаются коды программы.

4. .DATA — определяет начало сегмента данных.

5. DB,DW — директивы резервирующие один или несколько байтов: DB, или одно или несколько слов: DW.

.DATA

fibs DB 1,1,2,3,5,8,13

rus DB ‘Турбо Ассемблер

buf DB 80 DUP(0);резервируется 80 байтов,каждый обнуляется

int DW 65535;в двух байтах располагается число FFFFh.

Array DW 100 DUP (0);резервируется 100 слов

6. END — обозначает конец программы.

.CODE

MyPROG. ; точка входа (начало программы).

. ; команды программы

END MyPROG

7. ENDM — окончание блока или макроопределения

8. ENDP — обозначает конец подпрограммы.

9. EQU — присваивает символическому имени или строке значение выражения.

BlkSize EQU 512

BufBlks EQU 4

BufSize EQU BlkSize * BufBlks

10. LABEL — определяет метку соответствующего типа.

.DATA

m_byte LABEL BYTE;метка m_byte типа BYTE позволяет теперь

m_word DW 0;иметь доступ отдельно к каждому байту данных

.CODE;m_word типа WORD

mov [m_word],0204h

add [m_byte],’0′;теперь в m_word хранится код

add [m_byte+1],’0′;3234h,ASCII код ‘0’ равен 30h

11. LOCAL — определяет метки внутри макроопределений как локальные и в каждом макрорасширении вместо них ассемблер вставляет уникальные метки: ??XXXX, где XXXX = (0000. FFFF)h. Почему ??XXXX ? Да потому что никому не должно прийти в голову начинать символическое имя с . и транслятор смело может генерировать метки не боясь совпадений.

12. MACRO — задает макроопределение.

Swap MACRO a,b; a,b — параметры макро (ячейки памяти)


mov ax,b;данное макрооопределение позволяет делать

mov bx,a;обмен данными между ячейками памяти, в

mov a,ax;отличие от команды xchg ;

mov b,bx;нельзя mov a,b;

ENDM

Вызов этого макроса производится командой: Swap m,n

13. .MODEL — определяет размер памяти под данные и код программы.

.MODEL tiny;под программу,данные и стек отводится один общий сегмент (64 Kb).

14. PROC — определяет начало подрограммы.

Print PROC NEAR

;здесь команды подпрограммы

Print ENDP

call Print;вызов подпрграммы.

15. .STACK — определяет размер стека.

.STACK 200h; выделяет 512 байтов для стека.

16. .RADIX base — определяет систему счисления по умолчанию, где base — основание системы счисления: 2, 8, 10, 16.

.RADIX 8

oct = 77; oct равно 63d.

17. ; — начало комментария.

КОМАНДЫ ПЕРЕСЫЛКИ

1. MOV DST,SRC; переслать (SRC) в (DST). Здесь и далее содержимое регистра, например регистра AL будет обозначаться — (AL) или (al), а пересылка в комментарии будет обозначаться знаком FF, то адрес порта указывается косвенно, через содержимое регистра DX (специальная функция регистра общего назначения).

in al,0a5h;ввести в AL байт данных из ВУ с адресом порта A5h .

mov dx,379h;ввести в аккумулятор AL байт данных из

in al,dx;внешнего устройства с адресом порта 379h

7. OUT PORT, ACCUM; переслать из аккумулятора AL или AX байт или слово в ВУ с символическим адресом PORT.

out 0ffh,al;

mov dx,37Ah;переслать слово данных из AX в ВУ с адре-

out dx,ax;сом порта 37Ah

8. LEA RP,M; загрузить в регистр RP эффективный адрес (смещение) ячейки памяти с символическим адресом M.

lea di, rus; аналог этой команды — mov di, OFFSET rus.

АРИФМЕТИЧЕСКИЕ КОМАНДЫ

1. ADD DST, SRC; сложить содержимое SRC и DST и результат переслать в DST.

add al, [mem_byte]; mem_byte однобайтовая ячейка памяти

add [mem_word], dx; mem_word двухбайтовая ячейка памяти

2. INC DST; увеличить (DST) на 1 (инкремент (DST)).

inc si; (SI) SRC 0/1 0 0 0

DST = SRC 0 0 1 0

DST SRC и оба являются однобайтовыми числами, тогда:

DST: 1. (+127) 2. (+127)

SRC: — (+2) — (-2)

(+125) (OF)=0 (+129)? (OF)=1

Во втором примере результат превышает диапазон: -128

Структура программы на ассемблере

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

Каждая машинная команда состоит из двух частей:

  • операционной — определяющей, «что делать»;
  • операндной — определяющей объекты обработки, «с чем делать».

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

метка команда/директива операнд(ы) ;комментарии

При этом обязательным полем в строке является команда или директива.

Метка, команда/директива и операнды (если имеются) разделяются по крайней мере одним символом пробела или табуляции.

Если команду или директиву необходимо продолжить на следующей строке, то используется символ обратный слеш: \.

По умолчанию язык ассемблера не различает заглавные и строчные буквы в написании команд или директив.

Примеры строк кода:

Метки

Метка в языке ассемблера может содержать следующие символы:

  • все буквы латинского алфавита;
  • цифры от 0 до 9;
  • спецсимволы: _, @, $, ?.

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

Первым символом в метке должна быть буква или спецсимвол (но не цифра). Максимальная длина метки – 31 символ. Все метки, которые записываются в строке, не содержащей директиву ассемблера, должны заканчиваться двоеточием : .

Команды

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

Директивы

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

Операнды

Операнд – объект, над которым выполняется машинная команда или оператор языка программирования.
Команда может иметь один или два операнда, или вообще не иметь операндов. Число операндов неявно задается кодом команды.
Примеры:

  • Нет операндов ret ;Вернуться
  • Один операнд inc ecx ;Увеличить ecx
  • Два операнда add eax,12 ;Прибавить 12 к eax

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

В качестве операндов могут выступать

  • идентификаторы;
  • цепочки символов, заключенных в одинарные или двойные кавычки;
  • целые числа в двоичной, восьмеричной, десятичной или шестнадцатеричной системе счисления.
Идентификаторы

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

Правила записи идентификаторов.

  • Идентификатор может состоять из одного или нескольких символов.
  • В качестве символов можно использовать буквы латинского алфавита, цифры и некоторые специальные знаки: _, ?, $, @.
  • Идентификатор не может начинаться символом цифры.
  • Длина идентификатора может быть до 255 символов.
  • Транслятор воспринимает первые 32 символа идентификатора, а остальные игнорирует.
Комментарии

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

Структура программы на ассемблере

Программа, написанная на языке ассемблера, может состоять из нескольких частей, называемых модулями . В каждом модуле могут быть определены один или несколько сегментов данных, стека и кода. Любая законченная программа на ассемблере должна включать один главный, или основной, модуль, с которого начинается ее выполнение. Модуль может содержать сегменты кода, сегменты данных и стека, объявленные при помощи соответствующих директив. Перед объявлением сегментов нужно указать модель памяти при помощи директивы .MODEL.

Пример «ничего не делающей» программы на языке ассемблера:

В данной программе представлена всего одна команда микропроцессора. Эта команда RET . Она обеспечивает правильное окончание работы программы. В общем случае эта команда используется для выхода из процедуры.
Остальная часть программы относится к работе транслятора.
.686P — разрешены команды защищенного режима Pentium 6 (Pentium II). Данная директива выбирает поддерживаемый набор команд ассемблера, указывая модель процессора. Буква P, указанная в конце директивы, сообщает транслятору о работе процессора в защищенном режиме.
.MODEL FLAT, stdcall — плоская модель памяти. Эта модель памяти используется в операционной системе Windows. stdcall
.DATA — сегмент программы, содержащий данные.
.CODE — блок программы, содержащей код.
START — метка. В ассемблере метки играют большую роль, что не скажешь о современных языках высокого уровня.
END START — конец программы и сообщение транслятору, что начинать выполнение программы надо с метки START .
Каждый модуль должен содержать директиву END , отмечающую конец исходного кода программы. Все строки, которые следуют за директивой END , игнорируются. Если опустить директиву END , то генерируется ошибка.
Метка, указанная после директивы END , сообщает транслятору имя главного модуля, с которого начинается выполнение программы. Если программа содержит один модуль, метку после директивы END можно не указывать.

Оптимизация программ на ассемблере (часть 2)

Это в наше-то время говорить об оптимизации программ? Бросьте! Не лучше ли сосредоточиться на изучении классов MFC или технологии .NET? Современные компьютеры так мощны, что даже Windows XP оказывается бессильна затормозить их!

Илон Маск рекомендует:  Что такое код cpdf_curveto

Нынешние программисты к оптимизации относятся более чем скептически. Позволю себе привести несколько типичных высказываний:


» …я применяю относительно медленный и жадный до памяти язык Perl, поскольку на нем я фантастически продуктивен. В наше время быстрых процессоров и огромной памяти эффективность — другой зверь. Большую часть времени я ограничен вводом/выводом и не могу читать данные с диска или из сети так быстро, чтобы нагрузить процессор. Раньше, когда контекст был другим, я писал очень быстрые и маленькие программы на C. Это было важно. Теперь же важнее быстро писать, поскольку оптимизация может привести к столь малому росту быстродействия, что он просто не заметен «, — говорит Robert White;

» …а стоит ли тратить усилия на оптимизацию и чего этим можно достичь? Дело в том, что чем сильнее вы будете адаптировать вашу программу к заданной архитектуре, тем, с одной стороны, вы достигнете лучших результатов, а, с другой стороны, ваша программа не будет хорошо работать на других платформах. Более того, «глубокая» оптимизация может потребовать значительных усилий. Все это требует от пользователя точного понимания чего он хочет добиться и какой ценой «, — пишет в своей книге «Оптимизация программ под архитектуру CONVEX C» М. П. Крутиков;

  • » Честно говоря, я сам большой любитель “вылизывания” кода с целью минимизации используемой памяти и повышения быстродействия программ. Наверное, это рудименты времен работы на ЭВМ с оперативной памятью в 32 Кбайт. С тем большей уверенностью я отношу “эффективность” лишь на четвертое место в критериях качества программ «, — признается Алексей Малинин — автор цикла статей по программированию на Visual Basic в журнале «Компьютер Пресс».
  • С приведенными выше тезисами, действительно, невозможно не согласиться. Тем не менее, не стоит бросаться и в другую крайность. Начертавший на своем знамени лозунг «на эффективность — плевать» добьется только того, что плевать (причем дружно) станут не в эффективность, а в него самого. Не стоит переоценивать аппаратные мощности! И сегодня существуют задачи, которым не хватает производительности даже самых современных процессоров. Взять хотя бы моделирование различных физических процессов реального мира, обработку видео-, аудио- и графических изображений, распознавание текста… Да что угодно, вплоть до элементарного сжатия данных архиватором a la Super Win Zip!

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

    Цель — определяет средства . Вот из этого, на протяжении всей книги, мы и будем исходить. Ко всем оптимизирующим алгоритмам будут предъявляется следующие жесткие требования:

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

    оптимизация не должна увеличивать трудоемкость разработки (в т. ч. и тестирования) приложения более чем на 10%—15%, а в идеале, все критические алгоритмы желательно реализовать в виде отдельной библиотеки, использование которой не увеличивает трудоемкости разработки вообще;

    оптимизирующий алгоритм должен давать выигрыш не менее чем на 20%—25% в скорости выполнения. Приемы оптимизации, дающие выигрыш менее 20% в настоящей книге не рассматриваются вообще, т. к. в данном случае «овчинка выделки не стоит». Напротив, основной интерес представляют алгоритмы, увеличивающие производительность от двух до десяти (а то и более!) раз и при этом не требующие от программиста сколь ни будь значительных усилий. И такие алгоритмы, пускай это покажется удивительным, в природе все-таки есть!

  • оптимизация должна допускать безболезненное внесение изменений.
    Достаточно многие техники оптимизации «умерщвляют» программу, поскольку даже незначительная модификация оптимизированного кода «срубает» всю оптимизацию на корню. И пускай все переменные аккуратно распределены по регистрам, пускай тщательно распараллелен микрокод и задействованы все функциональные устройства процессора, пускай скорость работы программы не увеличить и на такт, а ее размер не сократить и на байт! Все это не в силах компенсировать утрату гибкости и жизнеспособности программы. Поэтому, мы будем говорить о тех, и только тех приемах оптимизации, которые безболезненно переносят даже кардинальную перестройку структуры программы. Во всяком случае, грамотную перестройку. (Понятное дело, что «кривые» руки угробят что угодно — против лома нет приема).
  • Согласитесь, что такая постановка вопроса очень многое меняет. Теперь никто не сможет заявить, что, дескать, лучше прикупить более мощный процессор, чем тратить усилия на оптимизацию. Ключевой момент предлагаемой концепции состоит в том, что никаких усилий на оптимизацию тратить как раз не надо . Ну… почти не надо, — как минимум вам придется прочесть эту книжку, а это какие ни какие, а все-таки усилия . Другой вопрос, что данная книга предлагает более или менее универсальные и вполне законченные решения, не требующие индивидуальной подгонки под каждую решаемую задачу.

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

    И в заключении позвольте привести еще одну цитату:

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

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

    О чем и для кого предназначена эта книга

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

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

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

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

    Материал книги в основном ориентирован на микропроцессоры AMD Athlon и Intel Pentium-II, Pentium-III и Pentium-4, но местами описываются и более ранние процессоры.

    Семь китов оптимизации или
    Жизненный цикл оптимизации

    Часто программист (даже высококвалифицированный!), обнаружив профилировщиком «узкие» места в программе, автоматически принимает решение о переносе соответствующих функций на ассемблер. А напрасно! Как мы еще убедимся (см. Часть III. » Смертельная схватка: ассемблер VS-компилятор «), разница в производительности между ручной и машинной оптимизацией в подавляющем большинстве случаев крайне мала. Очень может статься так, что улучшать уже будет нечего, — за исключением мелких, «косметических» огрехов, результат работы компилятора идеален и никакие старания не увеличат производительность, более чем на 3%—5%. Печально, если это обстоятельство выясняется лишь после переноса одной или нескольких таких функций на ассемблер. Потрачено время, затрачены силы… и все это впустую. Обидно, да?

    Прежде, чем приступать к ручной оптимизации не мешало бы выяснить: насколько не оптимален код, сгенерированный компилятором, и оценить имеющийся резерв производительности. Но не стоит бросаться в другую крайность и полагать, что компилятор всегда генерирует оптимальный или близкий к тому код. Отнюдь! Все зависит от того, насколько хорошо вычислительный алгоритм ложиться в контекст языка высокого уровня. Некоторые задачи решаются одной машинной инструкцией, но целой группой команд на языках Си и Паскаль. Наивно надеяться, что компилятор поймет физический смысл компилируемой программы и догадается заменить эту группу инструкций одной машинной командой. Нет! Он будет тупо транслировать каждую инструкцию в одну или (чаще всего) несколько машинных команд, со всеми вытекающими отсюда последствиями…

    Назовем ряд правил оптимизации.

    Правило I

    Прежде, чем оптимизировать код, обязательно следует иметь надежно работающий не оптимизированный вариант или «. put all your eggs in one basket, after making sure that you’ve built a really *good* basket» («…прежде, чем класть все яйца в одну корзину — убедись, что ты построил действительно хорошую корзину «). Таким образом прежде, чем приступать к оптимизации программы, убедись, что программа вообще-то работает.

    Создание оптимизированного кода «на ходу», по мере написания программы, невозможно! Такова уж специфика планирования команд — внесение даже малейших изменений в алгоритм практически всегда оборачивается кардинальными переделками кода. Потому, приступайте к оптимизации только после тренировки на «кошках», — языке высокого уровня. Это поможет пояснить все неясности и «темные» места алгоритма. К тому же, при появлении ошибок в программе подозрение всегда падает именно на оптимизированные участки кода (оптимизированный код за редкими исключениями крайне ненагляден и чрезвычайно трудно читаем, потому его отладка — дело непростое), — вот тут-то и спасает «отлаженная кошка». Если после замены оптимизированного кода на не оптимизированный ошибки исчезнут, значит, и в самом деле виноват оптимизированный код. Ну, а если нет, то ищите их где-нибудь в другом месте.

    Правило II

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

    Правило III

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

    Правило IV

    Прежде, чем порываться переписывать программу на ассемблер, изучите ассемблерный листинг компилятора на предмет оценки его совершенства. Возможно, в неудовлетворительной производительности кода виноват не компилятор, а непосредственно сам процессор или подсистема памяти, например. Особенно это касается наукоемких приложений, жадных до математических расчетов и графических пакетов, нуждающихся в больших объемах памяти. Наивно думать, что перенос программы на ассемблер увеличит пропускную способность памяти или, скажем, заставит процессор вычислять синус угла быстрее. Получив ассемблерный листинг откомпилированной программы (для Microsoft Visual C++, например, это осуществляется посредством ключа /FA), бегло просмотрите его глазами на предмет поиска явных ляпов и откровенно глупых конструкций наподобие: MOV EAX, [EBX]\MOV [EBX], EAX. Обычно гораздо проще не писать ассемблерную реализацию с чистого листа, а вычищать уже сгенерированный компилятором код. Это требует гораздо меньше времени, а результат дает ничуть не худший.

    Правило V

    Если ассемблерный листинг, выданный компилятором, идеален, но программа без видимых причин все равно исполняется медленно, не отчаивайтесь, а загрузите ее в дизассемблер. Как уже отмечалось выше, оптимизаторы крайне неаккуратно подходят к выравниванию переходов и кладут их куда "глюк" на душу положит. Наибольшая производительность достигается при выравнивании переходов по адресам, кратным шестнадцати, и будет уж совсем хорошо, если все тело цикла целиком поместится в одну кэш-линейку (т. е. 32 байта). Впрочем, мы отвлеклись. Техника оптимизации машинного кода — тема совершенно другого разговора. Обратитесь к документации, распространяемой производителями процессоров — Intel и AMD.

    Правило VI

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

    Правило VII

    Если уж взялись писать на ассемблере, пишите максимально "красиво" и без излишнего трюкачества. Да, недокументированные возможности, нетрадиционные стили программирования, "черная магия", — все это безумно интересно и увлекательно, но… плохо переносимо, непонятно окружающим (в том числе и себе самому после возращения к исходному коду десятилетней давности) и вообще несет в себе массу проблем. Автор этих строк неоднократно обжигался на своих же собственных трюках, причем самое обидное, что трюки эти были вызваны отнюдь не "производственной необходимостью", а… ну, скажем так, "любовью к искусству". За любовь же, как известно, всегда приходится платить. Не повторяйте чужих ошибок! Не брезгуйте комментариями и непременно помещайте все ассемблерные функции в отдельный модуль. Никаких ассемблерных вставок — они практически непереносимы и создают очень много проблем при портировании приложений на другие платформы или даже при переходе на другой компилятор.

    Единственная предметная область, не только оправдывающая, но, прямо скажем, провоцирующая ассемблерные извращения, это — защита программ, но это уже тема совсем другого разговора…

    Распространенные заблуждения

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

    Заблуждение I

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

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

    Заблуждение II

    Перенос программы на ассемблер только в исключительных случаях увеличивает ее эффективность. При трансляции качественного исходного кода, оптимизирующие компиляторы отстают от идеальной ручной оптимизации не более чем на 10%—20%. Конечно, это весьма ощутимая величина, но все же не настолько, чтобы оправдать трудоемкость программирования на чистом ассемблере!

    Подробнее о сравнении качества машинной и ручной оптимизации см. Часть III. " Смертельная схватка: ассемблер VS-компилятор ".

    Заблуждение III

    Вообще говоря, кроме компиляторов, разрабатываемых Intel, никакие другие компиляторы не умеют генерировать оптимально спланированный с точки зрения микроархитектуры процессора код. Несколькими страницами далее (см. " Практический сеанс профилировки с VTune ") вы собственноручно сможете убедиться в этом, а пока же просто поверьте автору на слово.

    Под оптимальностью обычно понимается глобальный экстремум некоторой оценочной функции. При оптимизации по скорости ищут абсолютный минимум числа тактов. Очевидно, этот минимум зависит от входных данных. В лучшем случае компилятору можно передать данные тестового прогона (так называемый profiler feedback). На основании этих данных компилятор может более аккуратно присвоить частоты выполнения разным последовательностям инструкций, не более того. Компиляторы Intel ни коим образом не генерируют оптимального кода. Исходя из определения, оптимальная по скорости программа не может быть переписана с сохранением смысла так, что начнет исполняться быстрее. Мне приходилось переписывать вручную порожденный этим компилятором код так, что он становился быстрее.

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

    Тем не менее, современные процессоры с одной стороны достаточно умны и самостоятельно оптимизируют переданный им на выполнение код. С другой стороны оптимального кода для всех процессоров, все равно не существует и архитектурные особенности процессоров P-II, P-4, AMD K6 и Athlon отличаются друг от друга столь разительно, что все позывы к ручной оптимизации гибнут прямо на корю.

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

    Заблуждение IV

    Как гласит народная мудрость "Хорошо там, — где нас нет". Сам я, правда, ничего не оптимизирую под PowerPC, но хорошо знаком с людьми, разрабатывающими под него оптимизирующие компиляторы. И могу сказать, что они далеко не в восторге от его "закидонов", коих у него, поверьте уж, предостаточно.

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

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

    Хостинг в Европе для новичков (от 25 руб/мес) и VIP-хостинг для профессионалов (от 1000 руб/мес)

    Скидка 25% на все тарифы хостинга по промокоду STDCITF

    Создание и отладка программы на Ассемблере

    Создание и отладка программы на Ассемблере

    - ознакомление со структурой программы на языке Ассемблер;

    - приобретение навыков работы со средствами разработки программ на Ассемблере для IBM-совместимых ЭВМ

    - изучение основных команд процессора i8086 (пересылки данных, арифметики) и директив определения данных языка Ассемблер;

    Основные положения

    . Предварительно следует ознакомиться со структурой процессора и сегментной организацией памяти ОС MS-DOS (файлы Ассемблер.doc или asm_lab.doc).

    Инструментальные средства разработки программы на Ассемблере

    Инструментальные средства или системы программирования (СП), предназначены для автоматизации работы пользователя по подготовке и отладке программ. Фактически это комплекс программ или интегрированная среда, объединяющая несколько программ. Основу большинства систем программирования составляют 3 компонента: транслятор, редактор связей и отладчик. Последовательность работ по созданию программ на языке Ассемблер с помощью этих инструментальных средств отображена на рис.1.1.

    Рис.1.1 Процесс разработки ПО на Ассемблере

    С помощью редактора текста (Microsoft Word, NotePad или др.) создается исходный модуль программы – текстовый файл в коде ASCII, содержащий текст программы на исходном языке. Этому файлу присваивается расширение .asmдля Ассемблера. Транслятор переводит программу с языка программирования на язык машинных кодов и производит несколько модулей, в том числе объектный модуль с расширением .obj и текстовый файл, содержащий как программу на исходном языке, так и программу в кодах ЭВМ для просмотра (файл с расширением .lst). Транслятор с языка Ассемблер называется ассемблером. Редактор связей объединяет при необходимости полученный модуль с другими объектными модулями и приводит программу к виду, пригодному для загрузки в память и выполнения ее компьютером (загрузочный или исполняемый модуль с расширением .exe). Наконец, Отладчик загружает исполняемую программу в память ЭВМ и позволяет выполнить программу в режимах трассировки с отображением результатов, отладку в пошаговом режиме, в режиме с остановкой в любых отладочных точках программы. Промежуточным шагом на этапе создания исполняемого модуля может быть преобразование модуля с расширением .exe в модуль с расширением .comс помощью программы преобразователя EXE2BIN. Программы в форме .com занимают меньше места в памяти, поэтому они предпочтительнее в тех случаях, когда программа должна быть резидентной, то есть должна постоянно находиться в памяти.

    Существует несколько вариантов СП для Ассемблера. Наиболее популярны из них система фирмы Microsoft (ассемблер MASM, редактор связей LINK, отладчик SYMDEB [ ] ) и Turbo-система фирмы Borland (ассемблер TASM, редактор связей TLINK и мощный отладчик TD [ ]).

    Каждая программа должна быть обязательно оформлена в соответствии с требованиями выбранного ассемблера.

    Mov DS,AX

    mov AH,4Ch ; Выход из программы

    int 21h; в DOS.

    myproc endp;


    cseg ends; Конец сегмента программы.

    Stacksegment stack ‘stack’; Организация

    dw 128 dup (0); сегмента

    stack ends; стека.

    end myproc ; Конец программы

    Следует заметить, что транслятор MASM различает прописные и строчные буквы только при использовании специальных опций при вызове ассемблера. В приведенном тексте выделены ключевые слова и обязательные команды или фрагменты команд.

    Рассмотрим структуру программы. Оператор title позволяет следующий за ним текст вывести во все страницы листинга программы в качестве заголовка. Комментарии указываются после знака «;».

    Программа состоит из трех сегментов: программного cseg, сегмента данных data и сегмента стека stack. Имена сегментам даются произвольно. Каждый сегмент открывается оператором segment и закрывается оператором ends. Перед обоими операторами должно стоять имя сегмента. Порядок описания сегментов в большинстве случаев значения не имеет, но предпочтительнее данные, используемые в программе, располагать перед текстом программы.

    Слова ‘code’ и ‘stack’ указывают на класс сегментов. Сегменты, принадлежащие одному классу, загружаются в память рядом. Когда в памяти используется один сегмент данного класса, этот указатель не обязателен, но для программы – компоновщика LINK необходимо указать класс ‘code’.

    Текст сегмента данных в этом примере начинается с одной из типичных команд работы с данными. Команда

    String db “строка данных”,”$”

    определяет строку, значение которой приведено в кавычках, знак доллара необходим для указания конца строки. По этой команде в памяти выделяется место и в эту область заносится текст «строка данных».

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

    Собственно программа обычно состоит из процедур. Деление программы на процедуры не обязательно, но повышает ее наглядность и облегчает передачу управления подпрограммам и другим программным модулям. В примере программа содержит единственную процедуру myproc, открываемую оператором proc и закрываемую оператором endp c обязательным указанием имени процедуры. Последние две команды программного сегмента

    используются для организации возврата в DOS после выполнения программы. При этом вызывается процедура DOS с номером 21h (здесь h указывает на то, что код 21 представлен в 16-тиричном виде) и используется функция 4C, шестнадцатеричный номер которой помещается в регистр АН. Процедура завершает текущий процесс, возвращая указанный код завершения родительскому процессу. В процессе завершения освобождает всю выделенную процессу память, сбрасывает на диск буферы, закрывает все открытые дескрипторы и переда­ет управление обработчику завершения процесса.

    В сегментный регистр данных DS данные загружаются через регистр общего назначения AX. В в регистры CS и SS данные загружаются автоматически.

    Для правильности выделения стека и автоматической инициализации регистра сегмента стека СS в строке описания сегмента стека необходимо указать параметр stack и класс ‘stack’. Для сегмента стека в программе зарезервировано 128 слов памяти с помощью оператора dw. Для программ, не осуществляющих операций со стеком, определение регистра SS и выделение сегмента стека необязательны. Текст программы заканчивается обязательной директивой end.

    Пример программы (. ехе-файл), реализующей вычисление функции у=(a+d)*c/d:

    ; Вычисление функции у=(а+b)*c/d

    ; Формат данных – слово.

    stseg segment para stack 'stack' ;задание сегмента стека

    dw 16 dup(?) ; резервирование 16 слов памяти под стек

    dseg segment para ;задание сегмента данных

    a dw 10

    b dw 20

    c dw 30

    d dw 30

    y dw ? ;результат (2 байта)

    ostd dw ? ;остаток от деления

    cseg segment para ;начало сегмента кода

    lab1 proc far ; вызов процедуры

    assume cs: cseg, ds: dseg,ss: stseg

    push ds

    mov ax,0

    push ax

    mov ax,dseg

    mov ds,ax

    mov ax,a ; (a)

    add ax,b ; (a+b)

    imul c ; (a+b)*c

    idiv d ;(a+b)*c

    mov y,ax ; запись результата в память

    mov ost,dx ; запись остатка в память

    ret ;возврат изпроцедуры

    lab1 endp; конец описания процедуры

    cseg ends ; конец сегмента кода

    end lab1 ; конец программы

    Ret

    mov ah,4ch ;завершение выполнения программы

    Int 21h

    endlab1

    Оформление программы типа .com с использованием модели памяти:

    .model tiny;модель памяти для .com-программы

    .code ; начало сегмента

    org 100h ; под нужды ОС выделяется 256 байтов в

    lab1proc far

    movax,a

    subax,b

    imulc

    idivd

    movy,ax

    movost,dx

    Ret

    adw10

    bdw20

    cdw30

    ddw30

    ydw?

    ostdw?

    lab1endp

    endlab1

    Пример отладки программы в отладчике AFDPro:

    AX 0009 SI 0000 CS 2C8D IP 001A Stack +0 0000 Flags 3284 BX 0000 DI 0000 DS 2C8C +2 2C7A CX FFF6 BP 0000 ES 2C7A HS 2C7A +4 0003 OF DF IF SF ZF AF PF CF DX FFFF SP 001C SS 2C8A FS 2C7A +6 0002 0 0 1 1 0 0 1 0 +------------------------------------------------------------------------------- ¦CMD > ¦ 1 0 1 2 3 4 5 6 7 +------------------------------------------02-¦ DS:0000 0A 00 14 00 1E 00 1E 00 0017 A10600 MOV AX,[0000] ¦ DS:0008 00 00 00 00 00 00 00 00 001A F63E0800 ADD AX,[0002] ¦ DS:0010 1E B8 00 00 50 B8 8C 2C 001E 03C1 IMUL W/[0004] ¦ DS:0018 8E D8 A1 00 00 03 06 02 0020 A30900 IDIV W/[0006] ¦ DS:0020 00 F7 2E 04 00 8B C8 A1 0023 89160B00 MOV [0008],AX ¦ DS:0028 06 00 F6 3E 08 00 03 C1 0027 CB MOV [000A],DX ¦ DS:0030 A3 09 00 89 16 0B 00 CB 0028 0000 RET Far ¦ DS:0038 00 00 00 00 8F 4E 00 00 002A 0000 ADD [BX+SI],AL ¦ DS:0040 00 20 00 00 00 00 00 00 002C 8F ADD [BX+SI],AL ¦ DS:0048 00 00 00 00 00 00 00 00 -------------------------------------------------------------------------------- 2 0 1 2 3 4 5 6 7 8 9 A B C D E F ¦ DS:0000 03 00 02 00 FE FF 09 00 02 00 00 00 00 00 00 00 ¦. __.. . DS:0010 1E B8 00 00 50 B8 8C 2C 8E D8 A1 00 00 03 06 02 ¦.+..P+М, О+б. DS:0020 00 F7 2E 04 00 8B C8 A1 06 00 F6 3E 08 00 03 C1 ¦.ў. Л+б ..Ў>. - DS:0030 A3 09 00 89 16 0B 00 CB 00 00 00 00 8F 4E 00 00 ¦г..Й. - . ПN.. DS:0040 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ¦. . . -------------------------------------------------------------------------------- 1 Step 2ProcStep 3Retrieve 4Help ON 5BRK Menu 6 7 up 8 dn 9 le 10 ri

    Лабораторное задание

    Задание 1.Разработать программу вычисления функции y=f(a,b,c,d). Формат данных – байт, слово. Данные задаются в программе с помощью директив DBиDW. Результат записать в память. Правильность работы программы проверить с помощью отладчика.

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

    Программу следует набрать в любом текстовом редакторе, создающем файл в ASCII-кодах, (например, с помощью Блокнота) и сохранить с именем, например, Lab1.asm.

    Пусть для работы с программой используются транслятор Tasm, редактор связей Tlink и отладчик AFD. Настройте в свойствах этих программ снятие флажка Закрывать окно по завершении работы на вкладке Программа. Для удобства работы можно разместить эти программы и программу Lab1.asm в одном каталоге.

    Откомпилировать c получением листинга:

    набрать в командной строке Total Commander:

    Tasm.exe /l Lab1.asm

    Если ошибок при компиляции не обнаружено, будут созданы файлы листинга Lab1.lst и объектного кода Lab1.obj.

    Получить исполняемый файл, запустив Редактор связей:

    Tlink.exe Lab1.obj

    AFD.exe Lab1.exe

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

    Таблица 1. Варианты заданий.
    Вариант Функция Вариант Функция
    y=a+b-c/d*e y=(a+b)*c-d/e
    y=a-b/c+d*e y=a*b/c-d*e
    y=a*b+c/d-e y=a+b-c+d/e
    y=a/b+c-d*e y=(a+b)/c-d*e
    y=(a-b+c)/d*e y=a-b/c*d+e
    y=a-b+c/d*e y=(a-b)+c/d+e


    Задание 2.Настроить среду Asm Editor на работу с программами Tasm, Tlink и AFD.

    Кнопку трансляции свяжите с .bat-файлом, который

    · запускает Tasm с параметром, указывающим на исходную программу,

    · если трансляция прошла успешно, запускает Tlink для создания объектного файла, в противном случае открывает Блокнот с листингом программы.

    Оформить разработанную в соответствии с Заданием 1 программу в виде .сом –программы, используя операнды-байты. Отладить программу.

    Отчет должен содержать листинги программ и окна отладки программ на момент окончания их выполнения.

    Создание и отладка программы на Ассемблере

    - ознакомление со структурой программы на языке Ассемблер;

    - приобретение навыков работы со средствами разработки программ на Ассемблере для IBM-совместимых ЭВМ

    - изучение основных команд процессора i8086 (пересылки данных, арифметики) и директив определения данных языка Ассемблер;

    Основные положения

    . Предварительно следует ознакомиться со структурой процессора и сегментной организацией памяти ОС MS-DOS (файлы Ассемблер.doc или asm_lab.doc).

    Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого.

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

    Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ - конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой.

    Ассемблер с нуля. Часть 6. Первая программа.

    Наша первая программа на ассемблере.

    Наша первая программа на ассемблере будет в формате *.COM — как мы уже знаем, исполняемые файлы указанного формата очень крохотные (tiny) по размеру и состоят из одного сегмента, в котором размещаются код, данные и стек.

    Ещё мы знаем, что в указанном формате пишутся резидентные программы, драйверы и вирусы.

    Резидентная (TSR-программа, от англ. Terminate and Stay Resident) — это программа, которая после запуска передает управление операционной системе, но сама не завершается, а остаётся в оперативной памяти, реагируя на определённые действия пользователя. Например, при нажатии сочетания горячих клавиш делает снимок экрана.

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

    Наша первая программа выведет на экран монитора (консоль) надпись «Hello, World!». Итак, как говорил Юрий Алексеевич, поехали!

    Создаём исполняемый файл PRG.COM.

    Для достижения нашей цели делаем следующее.

    • Скачиваем с нашего сайта архив (DOS-1.rar) с предустановленными DOSBox и программами. Запускаем DOSBox. Стартует эмулятор MS-DOS и Norton Commander пятой версии.
    • В папке D:\TASM.2_0\TASM\ находим текстовый файл PRG.ASM. Это обычный текстовый файл, который можно создать
      с помощью любого текстового редактора, с расширением ASM вместо TXT.
    • В файл вносим код:
    • В папке D:\TASM.2_0\TASM\ находим «батник» ASM-COM.BAT со следующим текстом:

    Первая сторка — запуск транслятора с названием нашего файла с кодом, расположенного в одной директории с транслятором.

    Вторая строка — запуск компилятора с параметрами /t /x и название объектного файла — prg.obj, получившегося в результате выполнения первой команды.

    Чтобы посмотреть список всех возможных параметров с пояснениями для файлов tasm.exe и tlink.exe необходимо запустить эти программы без параметров. Если вы сделаете это, не выходя из оболочки NC, то, чтобы просмотреть чистое окно DOS нажмите Ctrl+O, чтобы вернуться в NC, нажмите сочетание клавиш повторно.

    • После запуска ASM-COM.BAT в этой же директории появится файл prg.com. Запустив его мы увидим сообщение «Hello World!» в окне MS-DOS (при необходимости просмотра, снова применяем Ctrl+O).

    Батник ASM-EXE.BAT предназначен для создания исполняемого файла формате *.EXE (предусматривает раздельную сегментацию для кода, данных и стека — наиболее распространённый формат исполняемых файлов DOS).

    Батник COMPLEX.BAT предназначен для создания исполняемых файлов из двух файлов кода (названия обязательно должны быть prg.asm, prg1.asm).

    Наша первая программа на ассемблере прекрасно работает!

    TASMED (Tasm Editor) — среда разработки приложений DOS на ассемблере.

    Выше мы рассмотрели стандартный подход к программированию на TASM в системе MS-DOS. Указанным алгоритмом создания программ можно пользоваться и далее.

    Для более удобной работы с кодом целесообразно применять какую-либо среду разработки. Среда разработки — это громко сказано для времён MS-DOS, правильнее сказать — специфический редактор.

    Можете попробывать TASMED в папке D:\UTILS\TASMED\. Программа уже настроена и готова к использованию.

    Первая программа на ассемблере в среде разработки TASMED.

    • подсветка ассемблерного синтаксиса;
    • возможность сохранения проектов под любым именем и в любой директории;
    • работа как с TASM, так и MASM.
    • только английский язык интерфейса, но английский программист должен знать лучше русского;
    • слишком много настроек для текстового редактора.
      Хотя, в принципе, настройки — не проблема. Основное, что необходимо настроить — это соответствующие пути:
      Options->External->Assembler
      Options->External->Linker
      В общем, разобраться не сложно.

    Практические советы: группирование проектов, русский язык в MS-DOS.

    Для удобства группирования создаваемых программ можно создать отдельную папку (мы создали папку PROJECTS) в которой создавать папки названий проектов, куда копировать соответствующие файлы. Пока у нас — это PRG.ASM, PRG.OBJ, PRG.EXE. Однако, в зависимости от параметров и наших программ их может быть больше (PRG.MAP, PRG.SYM и др.).

    В нашем случае, все программы, рассматриваемые в курсе обучения будут группироваться в директории D:\WORK в соответствующих папках. Например, наша первая программа в папке D:\WORK\PRGCOM\ (файлы prg.asm и prg.com). Папку D:\TASM.2_0\PROJECTS\ оставляем пустой для ваших проектов и экспериментов.

    Для того, чтобы в исходниках, которые вы будете просматривать в системе MS-DOS с использованием текстовых редакторов DOS и различных сред разработок нормально отображалась кириллица (например в комментариях к строкам кода) необходимо проделать следующие действия.

    1. Запустить драйвер русской раскладки клавиатуры. В нашей запущенной MS-DOS системе на базе DOSBox это файл C:\KEYRUS\keyrus.com. Впрочем, при запуске MS-DOS согласно нашим настройкам DOSBox, он запустится автоматически. При этом будет обеспечено не только отображение русского текста в текстовых редакторах, но и русскоязычная раскладка клавиатуры. Переключение раскладки Eng->Rus и наоборот — горячая клавиша «правый CTRL».
    2. Текст исходников необходимо писать в текстовых редакторах или средах разработки DOS.
    3. Если исходники пишутся в Windows редакторах, должна быть обеспечена русскоязычная кодировка текста — ASCII для DOS (CP866 или OEM866).

    Русскоязычная кодировка текста — программа просмотра файлов Total Commander. Русскоязычная кодировка текста — используем Notepad++.

    Конечно вопрос снимается сам собой, если комментарии писать на английском.

    В следующей статье мы разберём код нашей первой программы на ассемблере.

    Способы увеличения быстродействия программ

    Современные ЭВМ обладают очень большой мощностью. Скорость работы процессора (ЦП) современных ЭВМ измеряется гигагерцами, объём оперативной памяти гигабайтами, а современные интерфейсы устройств обеспечивают скорость обмена данными порядка, как минимум, нескольких сотен мегабайт в секунду [1]. Производительность, которая ещё несколько лет назад казалась «сказочной» в настоящее время стала нормой жизни.

    Однако параллельно росту мощности ЭВМ увеличивается и ресурсоёмкость приложений. У приложений совершенствуется функционал, интерфейс, возрастает объём обрабатываемых данных и как следствие системные требования. Поэтому вопрос об увеличении быстродействия приложений не теряет своей актуальности.

    Общие вопросы быстродействия программ.

    Быстродействие программ (ПО) зависит от многих факторов, но основными из них являются два:

    • Соотношение между реальными системными требованиями ПО и существующей аппаратной конфигурацией ЭВМ;
    • Алгоритмы работы ПО.

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

    1. Увеличивается производительность hardware, а вовсе не быстродействие ПО;
    2. Производительность hardware ограничена возможностями существующих в данный момент элементной базы и инженерных решений в данной области;
    3. Большие финансовые затраты на модернизацию и настройку по причине высокой стоимости комплектующих ЭВМ и услуг специалистов требуемой квалификации.

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

    1. Обеспечить работу нового ПО на уже существующем оборудовании;
    2. Разработать масштабируемое ПО;
    3. Значительно уменьшить финансовые и трудовые затраты при внедрении.

    Вместе с тем и у этого пути имеется ряд недостатков:

    1. Значительно усложняется процесс разработки ПО, так как более «быстрые» алгоритмы сложнее более «медленных» (на пример алгоритм бинарного поиска сложнее, чем алгоритм линейного поиска) [2];
    2. Реализация более сложных алгоритмов, как правило, требует привлечения специалистов более высокой квалификации;
    3. В случае работы с большими объёмами данных или выполнении задач требующих больших и сложных вычислений, ресурсоёмкость ПО всё равно остаются достаточно высокой. Несмотря, на какие либо способы увеличения быстродействия.

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

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

    Программная инженерия предоставляет несколько способов увеличения быстродействия программ. Рассмотрим их на примере языков программирования Delphi и Assembler.

    Увеличение быстродействия программ.

    Как было показано в предыдущем параграфе, можно увеличить быстродействие ПО соответствующим образом реализовав его алгоритмы. Количественным показателем быстродействия алгоритма (а, следовательно, и ПО) является время его выполнения, измеренное по специальной методике, так называемого профилирования [2]. Таким образом, в общем случае выбор наиболее «быстрых» алгоритмов сводится к измерению времени их выполнения и сравнении полученных результатов между собой. Такой способ анализа быстродействия является наиболее объективным [2]. На протяжении многих лет программистами был накоплен большой опыт профилирования, который позволяет сделать определённые выводы относительно возможности оптимизации быстродействия ПО ещё на стадии написания.

    Эти выводы были обобщены и представлены в виде определённых рекомендаций [2, 4]. Если программист будет следовать данным рекомендациям, то написанная программа вероятнее всего будет обладать большим быстродействием, чем в случае их игнорирования. Однако следует ещё раз подчеркнуть, что достоверные сведения о быстродействии может дать только профилирование. Это обусловлено тем, что быстродействие алгоритма определяет в первую очередь его конкретная реализация. Кроме того необходимо ещё раз отметить, что в отношении увеличения быстродействия ПО программная инженерия не всесильна (см. предыдущий параграф).

    В чём же состоят выше упомянутые рекомендации? Их краткое содержание применительно к языку программирования Delphi приведено ниже.

    1. При написании кода программ рекомендуется избегать процедур, состоящих из сотен строк. Практически всегда в них можно выделить блоки, которые лучше оформить в виде отдельной процедуры. Возможно, позже вы ей даже воспользуетесь где-то в другом месте. Не говоря уже о том, что это повышает понимание программы и вами, и другими программистами. К тому же так проще искать «узкие» места в программе.
    2. Использование оператора case (switch) вместо многократных if… then… else (if… else). Во втором варианте компилятор будет выполнять проверку условия столько раз, сколько у вас вариантов. В первом проверка выполняется лишь однажды.
    3. Некоторые действия могут быть довольно продолжительными, поэтому рекомендуется выносить за рамки цикла всё, что можно выполнить вне его, чтобы избежать большого числа повторений внутри цикла.
    4. В циклах типа for нужно стараться, чтобы значение счетчика уменьшалось до нуля, а не наоборот — начиналось с нуля. Это связано с особенностями процессора. Сравнение с нулём выполняется гораздо быстрее, чем с другим числом.
    5. Пользоваться типом Variant только при необходимости. Операции над этим типом сложнее, чем, например, над Integer или String.
    6. Не злоупотреблять «программированием на компонентах». В частности не использовать компонент TTreeView для хранения древовидных структур данных — он работает очень медленно и предназначен только для визуального отображения. В случае работы со структурами данных лучше использовать алгоритмы, созданные самостоятельно на основе фундаментальных.
    7. Сохранение и загрузка свойств компонентов с помощью методов ReadComponent и WriteComponent работает довольно медленно, поэтому по возможности рекомендуется сохранять и восстанавливать состояние программы между сеансами при помощи других способов.
    8. Заменить простой в реализации алгоритм на более сложный, но с большим быстродействием. Например, если заранее известно, что в списке для поиска будет много элементов, лучше его отсортировать и применять бинарный поиск вместо линейного.
    9. В критических с точки зрения быстродействия местах программы делать вставки на ассемблере. Команды ассемблера напрямую транслируются в машинный код. Таким образом, в отличие от высокоуровневых языков при компиляции отсутствует проблема синхронизации и ряд других негативных обстоятельств.

    Для других языков программирования вышеприведённый список может несколько отличаться, в частности отсутствием поддержки ассемблера и как следствие возможности оптимизации с его помощью (Java, Visual C# и др.).

    Особо следует отметить, что рекомендации 3 и 4 применяются не только для языков высокого уровня, но и для ассемблера. Помимо вышеуказанных для увеличения быстродействия программ написанных на ассемблере, в том числе и вставок, существуют следующие рекомендации [3]:

    1. Замещение универсальных инструкций на учитывающие конкретную ситуацию, например, замена умножения на степень двойки на команды сдвига (отказ от универсальности).
    2. Уменьшение количества передач управления в программе: за счет преобразования подпрограмм в макрокоманды для непосредственного включения в машинный код; за счет преобразования условных переходов так, чтобы условие перехода оказывалось истинным значительно реже, чем условие для его отсутствия; перемещение условий общего характера к началу разветвленной последовательности переходов; преобразование вызовов, сразу за которыми следует возврат в программу, в переходы («сращивание хвостов» и «устранение рекурсивных хвостов») и т.д.
    3. Максимальное использование всех доступных регистров за счет хранения в них рабочих значений всякий раз, когда это возможно, чтобы минимизировать число обращений к памяти, упаковка множественных значений или флагов в регистры и устранение излишних продвижений стека (особенно на входах и выходах подпрограмм).
    4. Использование специфических для данного процессора инструкций, например, инструкции засылки в стек непосредственного значения, которая имеется в процессоре 80286 и более поздних. Другие примеры – двухсловные строковые инструкции, команды перемножения 32-разрядных чисел, деление 64-разрядного на 32-разрядное число и умножение на непосредственное значение, которые реализованы в процессорах 80386 и 80486. Программа должна, разумеется, вначале определить, с каким типом процессора она работает!

    Заключение.

    Методы оптимизации быстродействия, рассмотренные в этой статье, были выработаны и проверены не одним поколением программистов и уже стали классическими. В то же время информационные технологии, в частности технологии программирования, постоянно развиваются. Появляются новые технологии, старые – модернизируются или уходят в прошлое. Растёт производительность аппаратной части ЭВМ и параллельно с этим растёт сложность и ресурсоёмкость выполняемых ими задач.

    Поэтому вопрос оптимизации быстродействия сейчас актуален также как и много лет назад. Более того он будет также актуален и в будущем.

    Список литературы и источников.

    1. Быстро и легко. Сборка, диагностика, оптимизация и апгрейд современного компьютера/ под ред. Печникова В.Н. Учебное пособие – М.: Лучшие книги, 2005;
    2. Бакнелл Д. Фундаментальные алгоритмы и структуры данных в Delphi. М.: ООО «ДиаСофтЮП»; СПб.: Питер, 2006;
    3. Дункан Р. Оптимизация программ на ассемблере./ Дункан Р.//PC Magazine/Russian Edition №1/1992;
    4. Список пропускных способностей интерфейсов передачи данных;
    5. Гапанович А. Как сделать свою программу быстрой./Компьютерра Online.
    Илон Маск рекомендует:  ИТ-аутсорсинг выгоды и преимущества
    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL