Загальна оптимізація
Ось як деякі мої улюблені оптимізації. Я фактично збільшив терміни виконання та зменшив розміри програми, використовуючи їх.
Зазначте невеликі функції як 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;
};
Виділення факторів змінної за межами петель
Якщо змінна створена під час руху всередині циклу, перемістіть створення / розподіл до циклу. У більшості випадків змінну не потрібно виділяти під час кожної ітерації.
Постійні вирази фактора поза петлями
Якщо обчислення або змінне значення не залежить від індексу циклу, перемістіть його поза (до) циклу.
Введення / виведення в блоках
Читання та запис даних великими фрагментами (блоками). Чим більше, тим краще. Наприклад, читання одного octect в той час , менш ефективно , ніж читання тисячі двадцять чотири октетів з одним читанням.
Приклад:
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
префікса.
Нарешті, Код, як компілятор
Іноді компілятор може оптимізувати кілька невеликих операторів краще, ніж одну складну версію. Також допомагає написання коду для оптимізації компілятора. Якщо я хочу, щоб компілятор використовував спеціальні вказівки щодо передачі блоку, я напишу код, який виглядає так, що він повинен використовувати спеціальні інструкції.