Главная страница
Навигация по странице:

  • Глава 12. Эффективность и оптимизация программ 1. Общие понятия эффективности

  • 2. Оптимизирующие компиляторы

  • Оценивайте возможное улучшение

  • 4. Эффективность выполнения программ

  • 5. Оптимизация использования памяти

  • 6. Некоторые приёмы повышения эффективности программ 1.Вычисление констант

  • 2.Инициирование переменных

  • 3.Арифметические операции

  • 4. Арифметика с фиксированной точкой

  • 5. Смешанные типы данных

  • 6. Способ устранения ошибок

  • 7. Выравнивание десятичных чисел

  • 8. Упорядочивание памяти

  • 12. Условные и логические выражения

  • 14. Использование сведения о машине и компиляторе

  • 7. Советы программисту по оптимизации программ

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


    Скачать 7.57 Mb.
    НазваниеУчебное пособие по дисциплине технология разработки программного обеспечения специальность Программирование в компьютерных системах
    Анкоркурсовая работа
    Дата08.01.2023
    Размер7.57 Mb.
    Формат файлаdoc
    Имя файла2_5397965015586183048-7.doc
    ТипУчебное пособие
    #877236
    страница18 из 30
    1   ...   14   15   16   17   18   19   20   21   ...   30

    6. Аспектно-ориентированное программирование



    Аспектно-ориентированное программирование (АОП) — парадигма программирования, основанная на идее разделения функциональности для улучшения разбиения программы на модули. Методология аспектно-ориентированного программирования была предложена группой инженеров исследовательского центра Xerox PARC под руководством Грегора Кичалеса (Gregor Kiczales). Ими же был разработан аспектно-ориентированное расширение для языка Java, получившее название AspectJ — (2001 год).

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

    Все языки АОП предоставляют средства для выделения сквозной функциональности в отдельную сущность. Так как AspectJ является родоначальником этого направления, используемые в этом расширении концепции распространились на большинство языков АОП. Основные понятия АОП:

    • Аспект ( aspect) — модуль или класс, реализующий сквозную функциональность. Аспект изменяет поведение остального кода, применяя совет в точках соединения, определённых некоторым срезом.

    • Совет ( advice) — средство оформления кода, который должен быть вызван из точки соединения. Совет может быть выполнен до, после или вместо точки соединения.

    • Точка соединения ( join point) — точка в выполняемой программе, где следует применить совет. Многие реализации АОП позволяют использовать вызовы методов и обращения к полям объекта в качестве точек соединения.

    • Срез ( pointcut) — набор точек соединения. Срез определяет, подходит ли данная точка соединения к данному совету. Самые удобные реализации АОП используют для определения срезов синтаксис основного языка (например, в AspectJ применяются Java-cигнатуры) и позволяют их повторное использование с помощью переименования и комбинирования.

    • Внедрение ( introduction, введение) — изменение структуры класса и/или изменение иерархии наследования для добавления функциональности аспекта в инородный код. Обычно реализуется с помощью некоторого метаобъектного протокола ( metaobject protocol, MOP).

    Ведение лога и обработка ошибок — типичные примеры сквозной функциональности. Другие примеры: трассировка; авторизация и проверка прав доступа; контрактное программирование (в частности, проверка пред- и постусловий). Для программы, написанной в парадигме ООП, любая функциональность, по которой не была проведена декомпозиция, является сквозной.

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

    Контрольные вопросы


    1. Дайте понятие парадигмы программирования.

    2. Может ли язык программирования поддерживать сразу несколько парадигм?

    3. Охарактеризуйте процедурное программирование?

    4. Охарактеризуйте функциональное программирование?

    5. В чем особенность логического программирования?

    6. Что такое автоматное программирование?

    7. В чем особенность объектно-ориентированного программирования (ООП)?

    8. Какой язык был первым в ООП и когда он появился?

    9. Какое понятие является важнейшим в ООП?

    10. Поясните основные принципы ООП?

    11. Перечислите родственные ООП методологии.

    12. От какого принципа отказались в прототипном программировании?

    13. Какие особенности ООП приводят к снижению производительности программных систем?

    14. Охарактеризуйте обязательный набор синтаксических средств объектно-ориентированного языка программирования.

    15. В чем заключается аспектно-ориентированное программирование?


    Глава 12. Эффективность и оптимизация программ
    1. Общие понятия эффективности
    Основной задачей программирования является создание пра­вильных, а не эффективных программ. Эффективная программа не нужна, если она не обеспечивает правильных результатов. Это правило Ван Тассела. Эффективная, но неправиль­ная программа редко может быть сделана правильной, в то время как правильную, хотя и неэффективную программу можно опти­мизировать и сделать эффективной. Поэтому оптимизация являет­ся вторым этапом программирования. Первый этап — получение правильной программы.

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

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

    • если она нужна для работы,

    • если ее будут выпол­нять многократно и

    • если статус проекта и фирмы позволяет,

    тогда и только тогда следует рассмотреть возможность ее оптимизации.

    Обыч­но большая часть времени расходуется на выполнение очень небольшой части программы (<5% ее объема), называемой крити­ческой областью. Как правило, только критическая область объ­ектной программы оптимизируется программистом вручную. Погоня за эффективностью часто ведет к злоупотреблению. За­мечено, что программисты тратят огромное количество времени, думая и беспокоясь о работе некритических областей программы. Современные компьютеры отличаются высоким быстро­действием и очень мала разница во времени выполнения програм­мы, если некоторые, редко выполняемые операторы удается сде­лать эффективными. Экономию можно получить только за счет многократно выполняемых циклов.

    Некоторые программисты считают архаичной задачу написания эффективной программы. Это справедливо только в отношении не­больших программ, для выполнения которых используются ма­шины с высоким быстродействием и большим объемом памяти. Что же касается больших программ, то еще на стадии проектиро­вания определяются требуемые параметры, включающие время и емкость памяти. Для экономических задач емкость памяти часто более критичный параметр, чем время. Создатель системного программного обеспечения должен установить не­обходимые объем памяти и производительность каждого модуля, особенно при создании боль­ших программных проектов. Требования к эффективности программы определяются на стадии проектирования.

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

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

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

    Третий тип программ—программы, созданные не программи­стами, а научными работниками или администраторами. Время для этих людей важнее всего. Здесь эффективность имеет значение только для программ, которые должны уместиться в заданном объеме памяти и выполняться за приемлемое время.

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

    Многие методы, делающие программу эффективной, не наносят ущерба ее удобочитаемости. Эти методы следует использовать всегда. Но некоторые меры по повышению эффективности могут быть просто вредными для по­лучения удобочитаемой программы. Удобочитаемость программы более существенна, чем ее эффек­тивность. Дело в том, что удобочитаемую программу легче отла­живать, модифицировать и использовать. А всякую большую про­грамму обычно изменяет, модифицирует и применяет совсем не тот человек, который ее писал. Лишь в особых случаях программу следует делать более эффективной: программа: либо не помещается в памяти, либо слиш­ком долго выполняется. Или же программа должна быть включена в библиотеку и часто использоваться. В этом случае эффективность становится очень важным фактором и ей отдают предпочтение в ущерб удобочитаемости.
    2. Оптимизирующие компиляторы
    Эффективность важна на двух стадиях разработки програм­мы: компилирования и выполнения. Если компилятор работает быстро, то он обычно составляет программу, которая выполняется медленно. Компиляторы, создающие эффективную объектную про­грамму, обычно бывают большими и работают медленно, так как оптимизируют объектную программу.

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

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

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

    Некоторые компиляторы оптимизируют выполнение программы. Имеются два типа такой оптимизации: машинно-зависимая и ма­шинно-независимая.

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

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

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

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

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

    Сегментация программ

    Программу, подлежащую оптимизации, следует разделить на подпрограммы. Оптимизация значительно облегчается, если про­грамма уже разделена на подпрограммы в соответствии с принци­пами структурного программирования. Как только подпрограммы выделены, следует ответить на три вопроса:

    • Какой процент общего времени использует каждая подпро­грамма?

    • Насколько (в процентном выражении) оптимизируется каж­дая подпрограмма?

    • Сколько человеко-часов необходимо для достижения этой цели?

    Каждый из этих вопросов подробно обсуждается далее.

    Время работы подпрограмм

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

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

    подпрограммы А — 5%, подпрограммы С — 15%,

    подпрограммы В — 60%, подпрограммы В — 20%.

    Очевидно, что, даже если подпрограмму А исключить совсем (что, вообще говоря, невозможно), мы смогли бы сэкономить толь­ко 5% общего времени работы программы. Таким образом, попыт­ку оптимизации, вероятно, следует предпринять в первую очередь в отношении подпрограммы В, где возможна наибольшая эконо­мия.

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

    Большинство программ имеет одну критическую точку, которая использует большую часть времени выполнения. Нередко ка­кая-либо малая часть программы расходует более 50% времени выполнения. Очевидно, что эту часть программы следует оптими­зировать в первую очередь.

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

    Оценивайте возможное улучшение

    Если точно определен процент общего времени, используемый подпрограммой, следует оценить ее возможное улучшение. Если подпрограмма расходует небольшой процент от общего времени программы и ее можно лишь незначительно улучшить, то не стоит тратить на это усилий. При определении возможного улучшения необходима или возможна только приблизительная оценка. Не так уж важно, со­ставляет возможное улучшение 20 или 25%. Однако существенно, будет оно 5 или 70%.

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

    Теперь можно установить некоторый параметр, который будет показывать возможное улучшение. Если у нас имеется под­программа, использующая 50% общего времени работы програм­мы, но дающая только 5% повышения эффективности, мы получим в результате 2,5% (.50 * .05 = .025) общего улучшения эффектив­ности. Другая подпрограмма, которая использует 10% общего вре­мени, но дает 50% повышения эффективности, обеспечивает 5% (.50 * .10 = .05) общего улучшения эффективности. Таким образом, вторую подпрограмму следует оптимизировать в первую очередь, так как это дает больший выигрыш. Итак, при выборе подпрограм­мы для оптимизации следует учитывать произведение процента потребления времени и процента улучшения.

    Необходимые усилия.

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

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

    Существуют два подхода к оптимизации имеющихся программ:

    • «чистка»

    • перепрограммирование.

    Оба подхода имеют как до­стоинства, так и недостатки.

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

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

    Шаги оптимизации

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

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

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

    4. Применяйте локальную оптимизацию в критических областях.

    5. Слишком часто программисты тратят много времени, улучшая редко используемую программу.


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

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

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

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

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

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

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

    • стоимость улучшения програм­мы,

    • частота ее использования,

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

    • способ компилирования раз­личных операторов.

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

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

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

    Оверлейность программы

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

    Виртуальная память

    Возможность оверлейности реализуется также при использова­нии виртуальной памяти. Программисты полагают, что они имеют в своем распоряжении оперативную память очень большого объема даже на машине со сравнительно небольшим размером памяти. Однако, если для ра­боты нужна часть программы, которой нет в оперативной памяти, теряется время: недостающая часть считывается с диска. Чем ре­же возникает такая ситуация, тем быстрее будет выполняться программа.

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

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

    Советы:

    1. Структурированные программы и использование модулей увеличат локализованность.

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

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

    4. Обращайтесь к данным для считывания (или для записи) в порядке их расположения в памяти. Например, если массивы рас­положены в памяти по столбцам, выполните вначале все обращения к одному столбцу, прежде чем перейти к следующему.

    5. Данные, не являющиеся массивом, можно разместить рядом, перегруппировав их описания. Лучше всего располагать рядом наиболее часто используемые массивы.

    Эквивалентность

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

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

    Использование циклов

    Использование циклов для повторения последовательности опе­раторов является обычным способом экономии памяти. Разные части программ нередко содержат одинаковые последовательности операторов. Если программист стремится к экономному расходованию памяти, он должен найти одинаковые части программы, ко­торые могут быть преобразованы в циклы. Циклы требуют некоторого дополнительного количества памя­ти на инициирование, проверку, изменение индекса и установку всех констант. Однако уменьшение общего объема памяти за счет удаления повторяющихся команд весьма значительно. Не много­кратно повторяющиеся последовательности операторов, требующие сложной организации циклов, часто можно писать последователь­но, а не итеративно.
    6. Некоторые приёмы повышения эффективности программ
    1.Вычисление констант

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

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

    2.Инициирование переменных

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

    Инициируйте переменные во время компилирования.

    3.Арифметические операции

    Арифметические операции выполняются с различной скоростью. Полезно знать, какие операции выполняются быстрее, так как иногда бывает целесообразно заменить одну операцию другой. Пе­речислим математические операции в порядке возрастания време­ни их выполнения: 1) сложение или вычитание, 2) умножение, 3) деление, 4) возведение в степень.

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

    Сложение выполняется быстрее, чем умножение, поэтому умно­жение на небольшое целое число следует заменять сложением. Так, 3*1 должно быть заменено на 1+1+1. Если в выражении не все числа являются целыми, то при замене может быть утеряна точ­ность. Ошибка округления действительных чисел имеет тенден­цию накапливаться, а не уменьшаться. Так, если К — действитель­ное число, а I — целое, то 1*К более правильно, чем К+К+К+К— (I раз).

    Преобразование уравнений может привести к исключению опе­раций. Например, выражение Х = 2*Y+(А—1)/Р+2*Т можно за­менить уравнением

    Х = 2*(Y+Т) + (А—1)/Р, что исключает одну операцию умножения.

    Поскольку деление является более медленной операцией, всюду, где возможно, его следует заменять умножением. Умножение вы­полняется по меньшей мере в два раза быстрее деления. Исключайте деление из вашей программы всюду, где это возможно: вме­сто А/5.0 пишите А*0.2.

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

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

    Медленный способ: А**8.0 или А**Р, где Р — число с плаваю­щей точкой.

    Более быстрый способ: А**8 или А**1, где I — целое число.

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

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

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

    Умножение выполняется значительно быстрее возведения в сте­пень, поэтому, если показатель степени — небольшое целое число, то операцию возведения в степень следует заменять несколькими операциями умножения:

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

    Заменяйте Х**2 на Х*Х Заменяйте Х**3 на Х*Х*Х Заменяйте Х**4 на (Х*Х)#(Х*Х)

    или на (((Х*Х)*Х)*Х)

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

    4. Арифметика с фиксированной точкой

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

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

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

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

    5. Смешанные типы данных

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

    Избегайте смешанных типов данных,

    6. Способ устранения ошибок

    В некоторых простых компиляторах следует тщательно выби­рать тип используемых констант. Например, А = 0 (неэффективно) А = 0.0 ( эффективно).

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

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

    7. Выравнивание десятичных чисел

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

    8. Упорядочивание памяти

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

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

    9. Группировка

    При выполнении операций одного приоритета над операндами разного типа следует группировать операнды одного типа, заклю­чая их в круглые скобки. Например, если операнд I относится к типу 1, операнд А — к типу 2, операнд К — к типу 3, то выражение вида 1*А*1*К*А* К* I следует записать как ((1*1*1)* А*А)* К*К.

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

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

    Например,

    Медленный способ:

    В = А*1

    С=(А+1)*2.0

    Д = А* А/1

    Эти операторы используют переменные А и I несколько раз. Лучше преобразовать один раз переменную I в переменную типа А и затем пользоваться новой переменной.

    Более быстрый способ:

    А1 = 1

    В=А*А1

    С=(А+А1)*2.0

    Д=А*А/А1

    Здесь переменную I следует преобразовать не три раза, а толь­ко один.

    10. Исключение циклов

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

    РОLY =((А(1)*Х+А(2))*Х+А(3))*Х+А(4)

    выполняется быстрее, чем в цикле:

    POLY=A(I)

    DO 1 I=2,4

    1 РОLY = РОLY*Х+А(1)

    Кроме того, в каждом языке есть свои особенности инициирова­ния массивов.

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

    11. Организация циклов

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

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

    Поэтому уделяйте основное внимание циклам. Разберем при­мер:
    Цикл А 100 итераций

    Цикл В 100 итераций

    Цикл С 100 итераций

    Конец С

    Конец В

    Конец А
    Рассмотрим вначале цикл С. Любая экономия, даже самая ма­лая, будет здесь увеличиваться в 100*10О*10О=1 000 000 раз, т. е. коэффициент улучшения равен одному миллиону. Очень малое улучшение внутри цикла С гораздо выгоднее, чем значительное улучшение вне его. Затем рассмотрите цикл В, и, наконец, цикл А.

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

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

    Оптимизируйте сначала внутренние циклы.

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

    12. Условные и логические выражения

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

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

    13. Ввод-вывод

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

    14. Использование сведения о машине и компиляторе

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

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

    Номера операторов записывают в пяти строках таблицы соглас­но последней цифре номера оператора. Номера операторов, окан­чивающиеся на 0 или 1, размещаются в первой строке, на 2 или .4 — во второй строке, на 4 или 5 — в третьей и т. д. Таким обра­зом, если номера операторов распределены равномерно, сокраща­ется время компилирования.

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

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


    • Если программа неправильна, не имеет значения, какова ее эффективность.

    • Определяйте требования к эффективности программы на стадии проектирования.

    • Удобочитаемость программы обычно более важна, чем эффек­тивность.

    • Используйте оптимизирующий компилятор. Инициируйте переменные во время компилирования.

    • Избегайте смешанных типов данных.

    • Оптимизируйте сначала внутренние циклы.

    • Используйте для индексации наиболее предпочтительный тип данных.

    • Группируйте записи в эффективные блоки для ввода-вывода.

    • Используйте загрузочные модули.


    Контрольные вопросы



    1. Что является наиболее важным при написании эффективных программ?

    2. Какой тип программ следует оптимизировать? Какой тип программ оптимизировать не надо?

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

    4. В каком случае использовать цикл менее эффективно, чем программировать последовательно?

    5. Как следует располагать вложенные циклы, чтобы сократить число инициирований и проверок цикла?

    6. Какие области программы наиболее выгодны для оптими­зации?

    7. Расположите нижеуказанные операции по скорости их вы­полнения— от самой быстрой к самой медленной:

    1) деление, 4) извлечение корня,

    2) сложение, 5) возведение в степень,

    3) умножение, 6) вычитание.

    1. Имеется ли оптимизирующий компилятор, пригодный для нашего языка программирования? Если да, то какие типы оптимизации он выполняет?

    2. Имеется ли в вашем компиляторе версия, минимизирующая использование объема памяти в объектной программе за счет снижения скорости выполнения? Можно ли повысить скорость выполнения за счет памяти?

    3. Выберите два различных оператора в вашем языке програм­мирования и разработайте тесты, выявляющие, какой оператор выполняется быстрее.

    4. Охарактеризуйте типы программ по степени важности для них эффективности.

    5. Почему удобочитаемость программ зачастую более существенна, чем эффективность?

    6. В чем особенности оптимизирующего компилятора?

    7. Что понимают под машинно-зависимой и машинно-независимой оптимизацией?

    8. Что такое критическая точка программы? Как это понятие используется при оптимизации?

    9. Охарактеризуйте приемы чистки и перепрограммирования программного кода.

    10. Каковы шаги оптимизации программы?

    11. Особенности оптимизации использования памяти?

    12. Какова роль описания переменных, инициирования переменных, вычисления констант, арифметических операций в повышении эффективности программ?

    13. Охарактеризуйте влияние циклов на эффективность программы.


    1   ...   14   15   16   17   18   19   20   21   ...   30


    написать администратору сайта