Введение
Когда приложение начинает «тормозить», сначала нужно понять, какой участок кода работает дольше всего. Измерить время выполнения в Qt можно без сторонних профилировщиков: для этого есть встроенные классы. В статье разберём, как замерить время выполнения функции или отдельного блока кода, чем QElapsedTimer лучше QTime и как не ошибиться при оценке результатов.
Старый способ: QTime
Во многих примерах в сети для замера используется класс QTime. Он запускает отсчёт методом start() и возвращает прошедшие миллисекунды через elapsed():
#include <QTime>
#include <QDebug>
QTime timer;
// Начало измерения
timer.start();
// Участок кода для измерения
// Завершение измерения и вывод в миллисекундах
qDebug() << "Time:" << timer.elapsed() << "ms";
Этот код работает в Qt 5, но у него есть недостатки. QTime опирается на системные часы. Если во время замера время на компьютере изменится (синхронизация по сети, переход на летнее время), результат окажется неверным. А в Qt 6 методы start(), restart() и elapsed() у QTime удалены, поэтому такой код просто не скомпилируется.
Как измерить время выполнения с QElapsedTimer
Для измерения интервалов в Qt есть специальный класс QElapsedTimer. Он использует монотонные часы: они не зависят от системного времени и всегда идут только вперёд. Класс появился в Qt 4.7 и одинаково работает в Qt 5 и Qt 6.
#include <QElapsedTimer>
#include <QDebug>
QElapsedTimer timer;
timer.start();
// Участок кода для измерения
heavyCalculation();
qDebug() << "Время выполнения:" << timer.elapsed() << "мс";
Метод elapsed() возвращает число миллисекунд с момента вызова start(). Результат имеет тип qint64, так что переполнения не будет даже при очень долгих замерах.
Наносекундная точность
Миллисекунд бывает мало, если функция выполняется очень быстро. Для таких случаев есть метод nsecsElapsed(). Он возвращает время в наносекундах:
QElapsedTimer timer;
timer.start();
quickFunction();
qint64 ns = timer.nsecsElapsed();
qDebug() << "Время выполнения:" << ns / 1000.0 << "мкс";
Реальная точность зависит от платформы. Узнать, какие часы использует таймер, можно через статический метод QElapsedTimer::clockType(). Проверить, монотонные ли они, поможет QElapsedTimer::isMonotonic().
Замер нескольких этапов
Часто нужно измерить не один блок, а несколько шагов подряд. Метод restart() возвращает прошедшее время и сразу запускает отсчёт заново. Это удобно для поэтапных замеров:
QElapsedTimer timer;
timer.start();
loadData();
qDebug() << "Загрузка:" << timer.restart() << "мс";
processData();
qDebug() << "Обработка:" << timer.restart() << "мс";
saveResults();
qDebug() << "Сохранение:" << timer.elapsed() << "мс";
Так сразу видно, какой этап занимает больше всего времени и где стоит искать узкое место.
Контроль таймаута
QElapsedTimer пригодится не только для замеров. С его помощью удобно ограничивать время работы цикла. Метод hasExpired() возвращает true, если с момента запуска прошло больше указанного числа миллисекунд:
QElapsedTimer timer;
timer.start();
while (!dataReady()) {
if (timer.hasExpired(5000)) {
qWarning() << "Данные не получены за 5 секунд";
break;
}
QCoreApplication::processEvents();
}
Вспомогательный класс для замеров
Чтобы не повторять одинаковый код в каждой функции, можно написать небольшой класс. Он замеряет время выполнения своей области видимости: таймер запускается в конструкторе, а результат выводится в деструкторе.
#include <QElapsedTimer>
#include <QDebug>
class ScopedTimer
{
public:
explicit ScopedTimer(const char *name) : m_name(name)
{
m_timer.start();
}
~ScopedTimer()
{
qDebug() << m_name << "-" << m_timer.elapsed() << "мс";
}
private:
const char *m_name;
QElapsedTimer m_timer;
};
void parseFile()
{
ScopedTimer t("parseFile");
// ... код функции
}
Когда функция завершится, объект t будет уничтожен, и в консоли появится строка с результатом. Такой подход срабатывает и при досрочном выходе через return, и при выбросе исключения.
Как получить достоверный результат
- Собирайте проект в режиме Release. В Debug-сборке нет оптимизаций, и время выполнения может отличаться в несколько раз.
- Повторяйте замер несколько раз и берите среднее или медиану. Первый запуск часто медленнее из-за прогрева кэшей.
- Следите, чтобы компилятор не выбросил измеряемый код. Если результат вычислений нигде не используется, оптимизатор может удалить весь участок.
- Не включайте в замер вывод в консоль. Вызов
qDebug()сам по себе занимает заметное время.
Заключение
Для измерения времени выполнения кода в Qt используйте QElapsedTimer. Он работает на монотонных часах, поддерживает наносекундную точность и подходит как для разовых замеров, так и для контроля таймаутов. Класс QTime для этой задачи устарел, а в Qt 6 его методы замера удалены. Для глубокого анализа производительности вместе с таймером полезно применять профилировщики, например Performance Analyzer в Qt Creator или Valgrind.