Ответ
Я использую профилировщики. Сначала — инструменты для замеров времени выполнения (профилировщики).
perf(Linux): Низкоуровневый профилировщик процессора.perf record -g ./my_cpp_app # Запись данных perf report -n --stdio # Анализ (можно использовать perf annotate для детализации по ассемблеру)ValgrindсCallgrind/Cachegrind: Хорош для детального анализа вызовов функций и промахов кэша. Визуализировать результат можно черезKCachegrind.- Встроенные профилировщики IDE: Например, VTune Profiler (Intel) или AMD uProf для анализа микроархитектурных событий.
- Инструментация кода: Для точечных замеров использую
std::chrono::high_resolution_clock.auto start = std::chrono::high_resolution_clock::now(); // ... критический участок кода ... auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration<double, std::milli>(end - start); std::cout << "Time: " << duration.count() << " msn";
На что смотрю в отчете профилировщика:
- Функции с наибольшим собственным (self) временем — где процессор проводит больше всего циклов.
- Горячие циклы — особенно вложенные.
- Частые аллокации памяти —
new/deleteилиmalloc/freeв циклах могут быть узким местом. - Блокировки и ожидание — высокое время в функциях мьютексов или условных переменных указывает на contention.
Пример из практики: В одном проекте профилировщик (perf) показал, что 40% времени тратится внутри std::map::find в горячем цикле. Замена на std::unordered_map дала ускорение в 2 раза для этой операции.
Ответ 18+ 🔞
Давай разберём этот цирк с профилировщиками, а то некоторые пишут код, который жрёт процессорное время, как не в себя, а потом удивляются, почему всё тормозит.
Вот с чего я начинаю — беру инструменты для замеров времени, то есть профилировщики. Это как рентген для твоего кода, сразу видно, где кость сломана.
perf(под Линуксом): Это низкоуровневая штука, которая прямо в процессор заглядывает. Просто, но мощно.perf record -g ./my_cpp_app # Записываешь, что происходит perf report -n --stdio # Смотришь отчёт и офигеваешь от увиденногоValgrindсCallgrindилиCachegrind: О, это отдельная песня. Идеально, чтобы понять, какие функции друг друга вызывают и где твой код промахивается мимо кэша процессора, как слепой кот. Результаты потом вKCachegrindзагружаешь — красота, сразу видно, где собака зарыта.- Профилировщики от производителей: Типа VTune от Intel или uProf от AMD. Для серьёзного копания в микроархитектуре — самое то. Когда нужно понять, не твой ли код виноват, что кэш процессора плачет кровавыми слезами.
- Ручные засечки в коде: Иногда нужно прицельно измерить кусок. Тут спасает
std::chrono.auto start = std::chrono::high_resolution_clock::now(); // ... вот этот подозрительный участок, который, возможно, сосёт все соки ... auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration<double, std::milli>(end - start); std::cout << "Выполнялось: " << duration.count() << " мс. Многовато, да?n";
А теперь, на что я, блядь, смотрю в этих отчётах, чтобы не сойти с ума:
- Функции, которые сами по себе жрут больше всего времени. Не те, что кого-то вызывают, а те, где процессор реально пашет. Это и есть главные кандидаты на оптимизацию.
- Горячие, особенно вложенные, циклы. Обычно там и сидит главный тормоз, прикинувшись безобидным
for. - Частые вызовы аллокации памяти. Если в цикле мелькают
new/deleteилиmalloc— это пиздец, а не код. Память выделять дорого, это тебе не хуй с горы скатить. - Всякие блокировки и ожидания. Если профиль показывает, что программа полжизни провела в
mutex.lock()— значит, у тебя драка потоков за ресурс, и все стоят в очереди, как за колбасой в девяностые. Доверия ебать ноль к такому коду.
Пример из жизни, чтобы было понятнее: Однажды perf показал, что 40% всего времени программа торчала внутри std::map::find в одном цикле. Я, конечно, сам от себя охуел. Заменил эту штуку на std::unordered_map — и этот кусок стал работать в два раза быстрее. Вот что значит посмотреть правде в глаза через профилировщик.