Прежде всего, не оптимизируйте слишком рано. Это не редкость тратить время на тщательную оптимизацию куска кода только для того, чтобы найти, что это не было узким местом, которое, по вашему мнению, было. Или, говоря иначе: «Прежде чем вы начнете быстро, заставьте его работать»
Изучите, есть ли какой-либо вариант для оптимизации алгоритма перед оптимизацией кода. Будет легче найти улучшение производительности, оптимизируя плохой алгоритм, чем оптимизировать код, а затем выбросить его, когда вы все равно измените алгоритм.
И выясните, почему вам нужно оптимизировать в первую очередь. Чего вы пытаетесь достичь? Если вы пытаетесь, скажем, улучшить время отклика на какое-то событие, если есть возможность изменить порядок выполнения, чтобы свести к минимуму критические области времени. Например, когда вы пытаетесь улучшить ответ на какое-либо внешнее прерывание, можете ли вы сделать какую-либо подготовку в мертвое время между событиями?
Как только вы решили, что вам нужно оптимизировать код, какой бит вы оптимизируете? Используйте профилировщик. Сфокусируйте свое внимание (в первую очередь) на областях, которые используются чаще всего.
Итак, что вы можете сделать в этих областях?
- свести к минимуму проверку состояния. Условия проверки (например, условия завершения циклов) - это время, которое не расходуется на фактическую обработку. Проверка состояния может быть сведена к минимуму с помощью таких методов, как циклическое разворачивание.
- В некоторых случаях проверка условий также может быть устранена с помощью указателей функций. Например, если вы используете конечный автомат, вы можете обнаружить, что внедрение обработчиков для отдельных состояний в виде небольших функций (с однородным прототипом) и сохранение «следующего состояния» путем хранения указателя функции следующего обработчика более эффективно, чем использование большой оператор switch с кодом обработчика, реализованным в отдельных случаях. YMMV.
- минимизировать вызовы функций. Функциональные вызовы обычно несут нагрузку на сохранение контекста (например, запись локальных переменных, содержащихся в регистрах в стек, сохранение указателя стека), поэтому, если вам не нужно делать вызов, это время сохраняется. Один из вариантов (если вы оптимизируете скорость, а не пространство) - использовать встроенные функции.
- Если вызовы функций неизбежны, минимизируйте данные, передаваемые этим функциям. Например, пропущенные указатели, вероятно, будут более эффективными, чем передача структур.
- При оптимизации скорости выберите типы данных, которые являются родным для вашей платформы. Например, на 32-битном процессоре, вероятно, будет более эффективно управлять 32-битными значениями, чем 8 или 16-битные значения. (обратите внимание - стоит проверить, что компилятор делает то, что, по вашему мнению, есть. У меня были ситуации, когда я обнаружил, что мой компилятор настаивал на выполнении 16-разрядной арифметики на 8-битных значениях со всеми преобразованиями и от конверсий поехать с ними)
- Найдите данные, которые могут быть предварительно рассчитаны, и либо вычислить во время инициализации, либо (еще лучше) во время компиляции. Например, при реализации CRC вы можете либо вычислить свои значения CRC «на лету» (с использованием полинома напрямую), что отлично подходит для размера (но ужасно для производительности), либо вы можете создать таблицу всех промежуточных значений - намного быстрее, в ущерб размеру.
- Локализовать данные. Если вы манипулируете блобом данных, часто ваш процессор может ускорить процесс, сохраняя все это в кеше. И ваш компилятор может использовать более короткие инструкции, которые подходят для более локализованных данных (например, инструкции, которые используют битовые смещения вместо 32 бит).
- В том же духе локализовать ваши функции. По тем же причинам.
- Выполните предположения, которые вы можете сделать о выполняемых вами операциях, и найдите способы их использования. Например, на 8-битной платформе, если единственная операция, которую вы делаете с 32-битным значением, является шагом, вы можете обнаружить, что вы можете сделать лучше, чем компилятор, путем вложения (или создания макроса) специально для этой цели, вместо использования обычной арифметической операции.
- Избегайте дорогостоящих инструкций - деление является ярким примером.
- Ключевое слово "register" может быть вашим другом (хотя, надеюсь, ваш компилятор имеет довольно хорошее представление об использовании вашего регистра).Если вы собираетесь использовать «register», вероятно, вам придется объявить локальные переменные, которые вы хотите «зарегистрировать» в первую очередь.
- Будьте совместимы с вашими типами данных. Если вы делаете арифметику на смеси типов данных (например, шорты и ints, удваиваете и плаваете), то компилятор добавляет неявные преобразования типов для каждого несоответствия. Это потраченные впустую циклы процессора, которые могут не понадобиться.
Большинство вариантов, перечисленных выше, могут использоваться как часть обычной практики без каких-либо негативных последствий. Однако, если вы действительно пытаетесь добиться максимальной производительности: - Исследуйте, где вы можете (безопасно) отключить проверку ошибок. Это не рекомендуется, но это сэкономит вам пространство и циклы. - Ручная часть вашего кода в ассемблере. Это, конечно, означает, что ваш код больше не переносится, но там, где это не проблема, вы можете найти сбережения здесь. Имейте в виду, что есть потенциально потерянное время для перемещения данных в регистры и из них, которые у вас имеются (т. Е. Для удовлетворения использования вашего компилятора в регистре). Также имейте в виду, что ваш компилятор должен выполнять очень хорошую работу самостоятельно. (конечно, есть исключения)
@onebyone, я люблю отслеживать vcountter. =] – strager 2009-09-17 21:22:39