Общие оптимизации
Вот некоторые из моих любимых оптимизаций. Я фактически увеличил время выполнения и уменьшил размеры программ, используя их.
Объявите небольшие функции как inline
или макросы
Каждый вызов функции (или метода) влечет за собой накладные расходы, например, добавление переменных в стек. Некоторые функции также могут вызывать накладные расходы при возврате. Неэффективная функция или метод имеет меньше операторов в своем содержании, чем совокупные накладные расходы. Это хорошие кандидаты для встраивания, будь то #define
макросы или inline
функции. (Да, я знаю, что inline
это всего лишь предложение, но в данном случае я рассматриваю его как напоминание компилятору.)
Удалить мертвый и избыточный код
Если код не используется или не влияет на результат программы, избавьтесь от него.
Упростите разработку алгоритмов
Однажды я удалил много ассемблерного кода и времени выполнения из программы, записав алгебраическое уравнение, которое она вычисляла, а затем упростил алгебраическое выражение. Реализация упрощенного алгебраического выражения занимала меньше места и времени, чем исходная функция.
Развертывание петли
У каждого цикла есть накладные расходы на проверку приращения и завершения. Чтобы получить оценку коэффициента производительности, подсчитайте количество инструкций в накладных расходах (минимум 3: приращение, проверка, переход к началу цикла) и разделите на количество инструкций внутри цикла. Чем меньше число, тем лучше.
Изменить: приведите пример развертывания цикла Перед:
unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
После раскрутки:
unsigned int sum = 0;
size_t i = 0;
**const size_t STATEMENTS_PER_LOOP = 8;**
for (i = 0; i < BYTES_TO_CHECKSUM; **i = i / STATEMENTS_PER_LOOP**)
{
sum += *buffer++; // 1
sum += *buffer++; // 2
sum += *buffer++; // 3
sum += *buffer++; // 4
sum += *buffer++; // 5
sum += *buffer++; // 6
sum += *buffer++; // 7
sum += *buffer++; // 8
}
// Handle the remainder:
for (; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
Это преимущество дает дополнительное преимущество: выполняется больше операторов, прежде чем процессор должен перезагрузить кэш команд.
У меня были потрясающие результаты, когда я развернул цикл до 32 операторов. Это было одним из узких мест, поскольку программе приходилось вычислять контрольную сумму для файла размером 2 ГБ. Эта оптимизация в сочетании с чтением блоков повысила производительность с 1 часа до 5 минут. Развертывание цикла также обеспечивало отличную производительность на языке ассемблера, мой memcpy
был намного быстрее, чем компилятор memcpy
. - ТМ
Сокращение if
заявлений
Процессоры ненавидят переходы или переходы, так как они заставляют процессор перезагружать свою очередь инструкций.
Логическая арифметика ( отредактировано: применен формат кода к фрагменту кода, добавлен пример)
Преобразуйте if
операторы в логические присваивания. Некоторые процессоры могут условно выполнять инструкции без ветвления:
bool status = true;
status = status && /* first test */;
status = status && /* second test */;
Короткое замыкание из Логических И оператора ( &&
) предотвращает выполнение тестов , если status
есть false
.
Пример:
struct Reader_Interface
{
virtual bool write(unsigned int value) = 0;
};
struct Rectangle
{
unsigned int origin_x;
unsigned int origin_y;
unsigned int height;
unsigned int width;
bool write(Reader_Interface * p_reader)
{
bool status = false;
if (p_reader)
{
status = p_reader->write(origin_x);
status = status && p_reader->write(origin_y);
status = status && p_reader->write(height);
status = status && p_reader->write(width);
}
return status;
};
Распределение переменных факторов вне циклов
Если переменная создается «на лету» внутри цикла, переместите создание / выделение до цикла. В большинстве случаев переменную не нужно выделять на каждой итерации.
Факторные константные выражения вне циклов
Если вычисление или значение переменной не зависит от индекса цикла, переместите его за пределы (до) цикла.
Ввод / вывод в блоках
Чтение и запись данных большими порциями (блоками). Больше лучше. Например, чтение одного октета за раз менее эффективно, чем чтение 1024 октета за одно чтение.
Пример:
static const char Menu_Text[] = "\n"
"1) Print\n"
"2) Insert new customer\n"
"3) Destroy\n"
"4) Launch Nasal Demons\n"
"Enter selection: ";
static const size_t Menu_Text_Length = sizeof(Menu_Text) - sizeof('\0');
//...
std::cout.write(Menu_Text, Menu_Text_Length);
Эффективность этой техники можно продемонстрировать визуально. :-)
Не используйте printf
семью для постоянных данных
Постоянные данные могут быть выведены с помощью блочной записи. Форматированная запись будет тратить время на сканирование текста для форматирования символов или обработки команд форматирования. См. Пример кода выше.
Отформатируйте в память, затем напишите
Отформатируйте в char
массив, используя несколько sprintf
, затем используйте fwrite
. Это также позволяет разбить структуру данных на «постоянные разделы» и переменные разделы. Подумайте о слиянии писем .
Объявить постоянный текст (строковые литералы) как static const
Когда переменные объявляются без символа static
, некоторые компиляторы могут выделять место в стеке и копировать данные из ПЗУ. Это две ненужные операции. Это можно исправить с помощью static
префикса.
Наконец, код, подобный компилятору
Иногда компилятор может оптимизировать несколько небольших операторов лучше, чем одну сложную версию. Также помогает написание кода, который помогает компилятору оптимизировать. Если я хочу, чтобы компилятор использовал специальные инструкции по передаче блоков, я напишу код, который выглядит так, как будто он должен использовать специальные инструкции.