Домой / Основные настройки / Оптимизация программного кода. Основные возможности оптимизации кода программистом и компилятором. Техника оптимизации программного кода CSS и HTML оптимизаторы

Оптимизация программного кода. Основные возможности оптимизации кода программистом и компилятором. Техника оптимизации программного кода CSS и HTML оптимизаторы

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

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

Оптимизации также могут классифицироваться в зависимости от области их применения на локальные (оператор, последовательность операторов, базовый блок), внутрипроцедурные, межпроцедурные, внутримодульные и глобальные (оптимизация всей программы, «оптимизация при сборке», «Link-time optimization»).

Отладка модуля с целью выявления логических ошибок

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

Отладка = Тестирование + Поиск ошибок + Редактирование.

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



Принципы и виды отладки программного средства

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

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

Модульное тестирование

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

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

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

При этом в ходе модульного тестирования решаются четыре основные задачи.

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

2. Поддержка разработки и рефакторинга низкоуровневой архитектуры системы и межмодульного взаимодействия – эта задача больше свойственна "легким" методологиям типа XP, где применяется принцип тестирования перед разработкой (Test-driven development), при котором основным источником требований для программного модуля является тест, написанный до самого модуля. Однако, даже при классической схеме тестирования модульные тесты могут выявить проблемы в дизайне системы и нелогичные или запутанные механизмы работы с модулем.

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

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

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

1. Не существует единых принципов определения того, что в точности является отдельным модулем.

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

Оптимизация программного кода называют изменение корректного кода, направленное на повышение его эффективности. «Оптимизация» подразумевает внесение небольших изменений, затрагивающих один класс, один метод, а чаще всего – несколько строк кода. Крупномасштабные изменения проекта или другие высокоуровневые способы повышения производительности оптимизацией не считаются. Это не самый эффективный способ повышения производительности. Улучшение архитектуры программы, перепроектирование классов и выбор более эффективного алгоритма - приводят к более впечатляющим результатам. Кроме того, оптимизация кода не самый легкий способ повысить производительность: легче купить новое оборудование или компилятор с улучшенным модулем оптимизации. Наконец, это не самый дешевый способ повысить производительность: на оптимизацию кода вручную изначально уходит много времени, а потом оптимизированный код труднее сопровождать.

Оптимизация программистом .

1). Расширить структуру данных добавлением дополнительной информации или изменить представление данных в этой структуре. 2). Вычисление результатов заранее и их хранение, для последующего использования. 3) . Применение упаковки данных. 4) . Внутренний цикл должен содержать min воз­можное количество проверок, а лучше всего только одну. 5) . Удаление безусловных переходов. 6) . Логические проверки должны быть располо­жены так, чтобы более быстрые условия, которые чаще оказываются пра­вильными, стояли перед более медленными условиями, которые реже оказываются правильными.7). Логическая функция на небольшом множестве исходных значений может быть заменена таблицей, представляющей это множество.8). Удаление одинаковых выражений. 9). Если два и более одинаковых выражения часто вычисляются подряд, их следует вынести в подпрограмму. 10) . Изменение типов данных может оказаться эффективным способом сокращения кода и повышения его быстродействия. 11) . Переписывание кода на низкоуровневом языке. При низком быстродействии код следует переписать на языке низкого уровня. Если вы пишите на С++, языком низкого уровня может быть Assembler. Переписывание кода на низкоуровневом языке обычно положительно влияет на быстродействие кода.

Оптимизация компилятором .

Методы оптимизации кода могут применяться на разных уровнях синтаксических конструкций: 1 ). на уровне оператора - большинство компиляторов выполняют некоторую оптимизацию на этом уровне. 2 ). на уровне блока – оптимизирующий компилятор выделяет операционную структуру программе путем конструирования ориентированного потокового графа программы, в кот каждая вершина представляет основной блок, а связи м/у вершинами представляют потоки управления. Большинство компиляторов производят оптимизацию на уровне блока. 3 ). на уровне цикла. 4 ). на уровне программы - наиболее сложный уровень оптимизации.

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

42. Оформление программ: основные пункты.

1) Описание реализации (язык, среда программирования, тестирования, предполагаемого исполнения):

· размеры в байтах, операциях, строках;

· размеры необходимой памяти под данные;

· требуемые технические ресурсы.

2) Обращение к подпрограмме (число, тип и порядок передачи входных параметров): способ передачи (по ссылке или по значению).

3) Описание возвращаемых параметров:

· параметр, который возвращает сама функция;

· описание входных параметров, которые функция изменяет.

4) Описание исключительных ситуаций и реакция программы на них.

5) Описание сообщений программы, если они есть.

6) Краткое описание алгоритма:

· если имеет название, то указать его;

· назвать источник или где приведен текст программы, откуда был взят алгоритм;

· можно привести блок-схему.

8) Описать все входные и выходные файлы и краткое их содержимое.

9) Привести структуры записи всех файлов:

· разбивка по полям записи;

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

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

Виды оптимизации

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

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

Выбор оптимизируемого участка

При оптимизации кода вручную существует еще одна проблема: нужно знать не только, каким образом проводить оптимизацию, но и в каком месте её применить. Обычно из-за разных факторов (медленные операции ввода, разница в скорости работы человека-оператора и машины и т.д.) лишь 10% кода занимают целых 90% времени выполнения (конечно, утверждение довольно умозрительно, и имеет сомнительное основание в виде закона Парето, однако выглядит довольно убедительно у Э. Таненбаума). Так как на оптимизацию придется расходовать дополнительное время, поэтому вместо попыток оптимизации всей программы лучше будет оптимизировать эти "критичные" ко времени выполнения 10%. Такой фрагмент кода называют узким местом или бутылочным горлышком (bottleneck), и для его определения используют специальные программы - профайлеры, которые позволяют замерять время работы различных частей программы.

На самом деле, на практике оптимизация зачастую проводится после этапа "хаотического" программирование (включающего такие вещи, как " ", "потом разберемся", "и так сойдет"), поэтому представляет собой смесь из собственно оптимизации, рефакторинга и исправления : упрощение "причудливых" конструкций – вроде strlen(path.c_str()), логических условий (a.x != 0 && a.x != 0) и т.п. Для таких оптимизаций профайлеры вряд ли пригодны. Однако для обнаружения таких мест можно использовать программы - средства поиска семантических ошибок на основе глубоко анализа исходного кода - ведь, как видно из второго примера, неэффективный код может быть следствием ошибок (как, например, опечатки в данном примере - скорее всего, имелось ввиду a.x != 0 && a.y != 0). Хороший обнаружит подобный код, и выведет предупреждающее сообщение.

Вред и польза оптимизаций

Практически ко всему в программировании надо относиться рационально, и оптимизации - не исключение. Считается, что неопытный программист на ассемблере обычно пишет код, который в 3-5 раз медленнее, чем код, сгенерированный компилятором (Зубков). Широко известно выражение по поводу ранних, довольно низкоуровневых (вроде борьбы за лишний оператор или переменную) оптимизаций, сформулированное Кнутом: "Преждевременная оптимизация - это корень всех бед".

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

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

Например, рассмотрим язык Си++ и т.н. Return-Value Optimization, суть которой в том, что компилятор может не создавать копии возвращаемого функцией временного объекта. Так как в этом случае компилятор "пропускает" копирование, этот прием также называется "Copy elision". Итак, следующий код:

#include struct C { C() {} C(const C&) { std::cout << "A copy was made.\n"; } }; C f() { return C(); } int main() { std::cout << "Hello World!\n"; C obj = f(); }

может иметь несколько вариантов вывода:

Hello World! A copy was made. A copy was made. Hello World! A copy was made. Hello World!

Как ни странно, все три варианта являются законными, так как в стандарте языка позволяется пропускать вызов копирующего конструктора в данном случае, даже если у конструктора есть побочные эффекты (§12.8 Копирование объектов класса, пункт 15).

Итог

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

PVS-Studio

Библиографический список

  • Э. Таненбаум. Архитектура компьютера.
  • Вирт Н. Построение компиляторов.
  • Кнут Д. Искусство программирования, том 1. Основные алгоритмы.
  • Зубков С.В. Assembler для DOS, Windows и UNIX.
  • Wikipedia. Code optimization.
  • Wikipedia.

Приветствую Вас, дорогие друзья, на блоге сайт. Большинство пользователей воспринимают сайты только внешне, оценивая дизайн и структуру, но за привлекательными внешними составляющими стоит множество внутренних законов и правил, которые определяются стандартами W3C. К внутренним составляющим относится html-код и CSS-стили (без отдельного функционала). Часто веб-мастеров больше заботит лишь внешнее представление сайта. Однако внимания требует и внутреннее содержимое страниц в виде html-кода, особенно когда речь идет о привлечении поискового трафика.

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

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

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

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

Основные этапы оптимизации кода сайта

  • Выделение заголовков h1-h6 – поисковые роботы в целом, поэтому не стоит забывать про правильное оформление текста.
  • Уменьшение размера кода – чем меньше кода, тем легче и быстрее загружается страница. С недавних пор, скорость загрузки страниц, стала важным фактором ранжирования в выдаче Google, о чем было официально заявлено.
  • Удаление вредоносного кода – на многих хостингах существует раздел антивирус, который сканирует файлы сайта и указывает путь к их решению. Отсутствие вредоносного кода делает сайт более предпочтительным для поисковиков.
  • Внутренняя оптимизация сайта – создание уникального текста, поддерживающего необходимую плотность ключевых слов.
  • – равномерно распределяет вес страниц и повышает трафик по низкочастотным запросам, особенно при пополнении нового контента.
  • Добавление мета тегов – title, keywords и description используются работами и отображаются на страницах поисковой выдачи. Правильно составленные мета-теги повышают релевантность страниц и привлекают пользователей.
  • Оптимизация изображений – каждой картинке должен быть подобран оптимальный формат (GIF, JPEG, PNG и PNG-24), а также прописаны alt и title.

Валидность кода сайта

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

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

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

Оптимизация каскадных таблиц стилей

Как и оптимизация html-кода, оптимизация стилей (css) является не менее важным мероприятием, которое упускается из виду многим веб-мастерами. Оптимизация css кода также ускоряет загрузку страниц сайта и экономит трафик. Поскольку css-файл весит до 100 Кб, многие веб-мастера не видят необходимости в его оптимизации, но если подумать, сколько трафика экономится при оптимизации файла в год, вы поймете, насколько ее недооценивали.

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

Настройка кодировки

Еще одним моментом, влияющим на продвижение сайта , является кодировка. Русскоязычный текст будет корректно отображаться только при условии правильной настройки кодировки, например, Windows-1251 или utf-8. При неправильной кодировке контента, знаки и символы будут искажены, что приведет к потере посетителей и замедленной индексации.

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

Внутренняя оптимизация сайта

В большинстве случаев грамотная внутренняя оптимизация ресурса может вывести сайт на лучшие позиции поисковиков без существенных усилий и работы со ссылочными биржами. Важна каждая мелочь, влияющая на его позиции. Постарайтесь создать такую структуру, которая будет понятна и пользователям, и поисковым системам. Многостраничные порталы должны содержать систематизированные разделы, в которых все страницы находятся не дальше 3-4 кликов. Желательно использовать навигационную цепочку и удобные для человеческого восприятия веб-адреса. К сожалению, не все скрипты CMS обладают таким функционалом.

Заключение

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

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

Иногда бывает сложно решить, какую конструкцию лучше использовать i++ или ++i, либо выбрать между конструкцией if-else и switch. В этой статье, написанной специально для сообщества iT works, представлены наиболее реальные средства оптимизации кода, которые должен знать каждый профессиональный программист.

Некоторые считают, что времена оптимизации на уровне кода прошли навсегда, однако это не так. Сейчас существует множество платформ в которых нет таких могущественных компиляторов как в Microsoft Visual Studio. Например шейдерные языки (hlsl, glsl) или код для CUDA, PlayStation3, SPU или мобильные платформы. В зависимости от организации кода, может в десятки раз отличаться его эффективность иногда из-за неэффективности компилятора, на чаще из-за доступа к памяти.

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

Если вы являетесь программистом в VS под Windows, то скорее всего со многими описанными приемами оптимизации компилятор эффективно справится. Обратите внимание на пункты работы с памятью, а так же я рекомендую ознакомиться с техникой Data oriented design . Некоторые советы по ее использования ищите в статье .

Итак, начнем:

1. Используйте векторизацию данных и векторные команды их обработки (например SSE в CPU или упаковывайте данные если используете шейдеры или CUDA). Это позволит использовать SIMD (Single Instruction, Multiple Data) архитектуру, что значительно повысит скорость вычислений. Если вы решите использовать этот метод, то не забывайте про выравнивание данных в памяти.

2. Эффективнее складывать и умножать в перемешку, чем сначала все сложить, а потом все умножить. Это происходит от того, что сложение и умножение выполняются разными модулями процессора и могут выполняться одновременно.
int a,b,c,k,m,n,t,f1,f2,f3,g1,g2,g3; a = b + с; k = m + n; t = a + k; f1 = f2 * f3; g1 = g2 * g3; Менее эффективно чем: a = b + с; f1 = f2 * f3; k = m + n; g1 = g2 * g3; t = a + k;

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

4.
Допустим есть. const float a = 100.0f; float some1 = some3 * 1.0f / a; float some2 = some4 * 1.0f / a; более эффективно написать не: const float a_inv = 1.0f / a; some1 = some3 * a_inv; some2 = some4 * a_inv; а так: some1 = some3 * (1.0f / a); some2 = some4 * (1.0f / a); Почему это будет эффективнее? Операторы с равным приоритетом выполняются последовательно. Это значит, что будет выполнено сначала умножение, а затем деление. Если же обрамить операцию деления в скобки, то ее выполнит компилятор, а в реальном времени будет выполняться только операция умножения. Что качается отличий варианта 3 от варианта 2, то в 3-ем варианте не создается дополнительной переменной, нет нужны глядя на код думать о том, что это за переменная. А эффективность 2-го и 3-го варианты будет одинаковой.

5. На больших объемах данных и вычислений на них float выгоднее чем double (из за cache miss"ов, см. пункт 3).

6.
a, b - любое выражение Func1, Func2 - функции, которые вызовутся для вычисления условия if(a && b) - нужно первым ставить менее вероятное условие, для большей эффективности if(a || b) - нужно первым ставить более вероятное условие, для большей эффективности if(Func1() && Func2()) - нужно ставить более быстрый оператор первым

7.
void Func(int* a) { int b = 10; Следующие строки одинаковые по эффективности (по времени выполнения): b = 100; *a = 100; } Это происходит по тому, что доступ к стековой переменной осуществляется по указателю на стек. Тоже идет разыменование указателя.

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

9.
int a[ 1000 ]; for(int i =0; i<1000; ++i) a[ i ] = 50; Значительно эффективнее будет: int* p = a; for(int i =0; i<1000; ++i, ++p) *p = 50;

10.
SomeClass* p; - указатель на массив элементов x = *(p++); - значительно эффективнее x = *(++p); По той же причине что и пункт 1. В первом случае будет осуществляться разыменование указателя и его инкремент параллельно, а во втором - последовательно.

11. Количество колонок двумерного массива желательно должно быть равно степени двойки. Это увеличит скорость работы с массивом. Это выравняет указатели на первые элементы каждой строки, что ускорит доступ к элементам.
int mas[ 10 ][ 16 - количество колонок желательно должно быть равно степени двойки ]

12.
u32 a; f32 b; b = (f32)(i32)a; - быстрее b = (f32)a;

13. Избегайте операции приведения типов.
float f; int a; float b = (float)a; - долго int m = (int)f; - очень долго

14. Разумно используйте операции округления:
только для unsigned: (u32)x are 10x times faster than "u32(floor(x))" u32(x + 1.0f) are 10x times faster than "u32(cell(x))" u32(x + 0.5f) are 10x times faster than "u32(round(x))"
15.
float f = 1.0f; *(int*)&f ^= 0x80000000; - быстрее чем f *= -1.0f;

16. Если в switch используются последовательные значения параметров case (case 0: case 1: case 2:...) то switch значительно эффективнее чем if-else. Это происходит за счет того, что при if-else будет вычисляться значение каждого условия, а в случае таких параметров в конструкции switch значение будет вычислено один раз, а затем будет переход сразу к нужному пункту.

17. Ветвления - это зло. Старайтесь сокращать их количество. Не делайте их внутри больших циклов. switch - это тоже ветвление. Процессор старается предсказывать результат условия (branch prediction) и если значение выражение почти всегда одно и то же, то ветвление не отразится на скорости выполнения кода. Однако в общем случае, предсказание ветвления будет не верно в 50% случаев, что будет замедлять выполнение алгоритма. Каждое ветвление - это переход к последовательности команд процессора. Такой переход ломает конвейер команд процессора и стоит достаточно дорого.

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

Const int NN = 12500000; const int N = 10; следующее плохо (200ms на моей машине): for(int i = 0; i < NN; ++i) { switch(i % N) { case 0: res += 10; break; case 3: res += 30; break; case 5: res += 50; break; case 6: res += 60; break; case 8: res += 80; break; } } гораздо лучше (120 ms на моей машине): const int arr = { 10, 0, 0, 30, 0, 50, 60, 0, 80, 0 }; for(int i = 0; i < NN; ++i) res += arr[ i % N ];

18. Рассмотрим пример. Двумерный спрайт содержит массив вершин vertex[ 4 ]. Гораздо эффективнее было бы сделать одно хранилище вершин, а в спрайте индекс смещения относительно первого элемента.
Это по памяти сэкономит 16 байт на каждый спрайт, а по скорости будет процентов на 30 быстрее проход по вершинам. Это data orientad design. Для С# он так же справедлив.

Основные направления оптимизаций:
1. Уменьшение числа ветвлений
2. Группировка данных по одинаковым типам в памяти (в C# никто еще не отменял массивы структур)
3. Уменьшение размеров структур

19. inline функции:
+ дает выигрыш в скорости
- увелечивает код
- добавляет в код зависимости (*.h файлов) при компиляции. Это увеличивает время и объем компиляции при изменении кода в функции

Факты:
1. компилятор может не встроить функцию (даже _forceinlie - нет горантии встраивания)
2. VS компилятор при включенной оптимизацией по скорости встраивает любые функции по своему усмотрению, даже если они не объявлены как inline.

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

20. Рассмотрим результат смены порядка переменных в структуре.
struct s1 { short int a; double b; int d; } sizeof(s1[ 10 ]) == 24 * 10 struct s2 { double b; int d; short int a; } sizeof(s1[ 10 ]) == 16 * 10 double по 8 выравнивается всегда

21. От перестановки мест слогаемых для float value, сумма меняется:
1e+8f + 1.23456 - 1e+8f == 0 но 1e+8f - 1e+8f + 1.23456 == 1.23456

22. Бинарный поиск не следует использовать на малом количестве элементов. Если количество элементов меньше чем 40-60 (это число может варьироваться от реализации алгоритмов, но имеет примерно такой порядок), бинарный поиск будет медленнее линейного.

23.
Можно так: bool b; int a = b ? x: y; Но быстрее: int b; (0 - false, -1 - true) int a = (x & b) | (y & ~b);

24.
int a, b; 1. int x = (a >= b ? 1: 0); 2. int x = (a >= b ? -1: 0); Можно заменить на: 1. int x = (b - a) >> 31; 2. int x = (b - a) & 0x80000000;

25.
i32 iIndex; Условие: if(iIndex < 0 && iIndex >= iSize) Можно заменить таким: if((u32)iIndex >= iSize) Условие: if(i >= min && i <= max) Можно заменить таким: if((u32)(i-min) <= (u32)max - min)

26. Выше был пример, как можно switch превратить в static const array и обращаться по индексу. Это применимо например для rtti (run time type identification). Если таким образом определены switch указателей на функции, то замена его доступом к нужной функции за константное время - может быть крайне полезна. То же самое - если это машина состояний. Вместо того, чтобы добавлять новый элемент в свитч, его можно добавлять в массив выше. Но помните про пункт 16.

Int func(int index) { switch(index) { case 0: return f_Func1(); case 3: return f_Func2(); case 4: return f_Func2(); .. case .. return f_FuncN(): } return 0; } заменить на: int func(int index) { static funcPtr array = { &f_Func1, NULL, &f_Func2, ... &f_FuncN } return array[ index ](); }

Дополнительно

Дополнительные рекомендации по написанию более эффективного кода, можно найти в статьях: