Когда код работает медленно, важно применять методичный подход к анализу и оптимизации. Вот профессиональная стратегия, которую я использую на практике.
Золотое правило: Не оптимизировать без профилирования.
Инструменты:
Пример использования perf:
perf record ./my_program
perf report
Что искать:
Пример вывода:
Overhead Command Shared Object Symbol
35.12% my_app my_app [.] _Z12process_dataPKdi
22.45% my_app libc.so.6 [.] malloc
18.76% my_app my_app [.] _Z9heavy_mathRi
Первый шаг: Убедиться в оптимальности алгоритмов.
Проблемные паттерны:
Пример оптимизации:
// Было: O(n^2)
for (auto& item : items) {
if (std::find(selected.begin(), selected.end(), item.id) != selected.end()) {
process(item);
}
}
// Стало: O(n)
std::unordered_set<int> selected_set(selected.begin(), selected.end());
for (auto& item : items) {
if (selected_set.count(item.id)) {
process(item);
}
}
Ключевые проблемы:
Пример улучшения:
// Было: множественные аллокации
std::vector<std::string> process() {
std::vector<std::string> result;
for (/*...*/) {
std::string item = create_item();
result.push_back(item); // возможны реаллокации
}
return result;
}
// Стало: резервирование памяти
std::vector<std::string> process() {
std::vector<std::string> result;
result.reserve(expected_count); // предотвращаем реаллокации
for (/*...*/) {
result.emplace_back(create_item()); // конструируем на месте
}
return result; // NRVO или move
}
Современные подходы:
Пример SIMD:
#include <immintrin.h>
void sum_arrays(float* a, float* b, float* c, size_t n) {
for (size_t i = 0; i < n; i += 8) {
__m256 av = _mm256_load_ps(a + i);
__m256 bv = _mm256_load_ps(b + i);
__m256 cv = _mm256_add_ps(av, bv);
_mm256_store_ps(c + i, cv);
}
}
Проблемные места:
Пример улучшения:
// Было: посимвольное чтение
std::ifstream file("data.bin");
char c;
while (file.get(c)) { /*...*/ }
// Стало: буферизованное чтение
std::vector<char> buffer(1024 * 1024);
while (file.read(buffer.data(), buffer.size())) {
size_t read = file.gcount();
// Обработка блока данных
}
Эффективные приёмы:
Пример:
// Было: деление в цикле
for (int i = 0; i < n; ++i) {
array[i] = i / divisor;
}
// Стало: умножение на обратное
float inv_divisor = 1.0f / divisor;
for (int i = 0; i < n; ++i) {
array[i] = i * inv_divisor;
}
Флаги для gcc/clang:
-O3
- агрессивная оптимизация-march=native
- использование специфичных инструкций CPU-flto
- межмодульная оптимизацияВажно: Всегда проверять, что оптимизации не ломают логику!
Принципы:
Пример:
// Плохо для кэша:
struct Item {
int id;
double data[100];
bool active;
};
// Лучше:
struct Item {
int id;
bool active;
double data[100]; // Большие поля в конец
};
Как анализировать:
g++ -O3 -S -masm=intel main.cpp -o main.s
Что искать:
Когда код работает медленно:
Помните: "Преждевременная оптимизация - корень всех зол" (Д. Кнут), но и запоздалая оптимизация может быть дорогостоящей. Балансируйте между читаемостью и производительностью.