Измерение времени в C++: практическое руководство по std::chrono для начинающих

Измерение времени в C++: практическое руководство по std::chrono для начинающих

Измерение времени в C++: практическое руководство по std::chrono для начинающих

Запрос «измерение времени в C++» стабильно популярен у начинающих и продолжающих разработчиков. Часто нужно понять, сколько работает алгоритм, где узкое место или как корректно реализовать таймаут. Стандартная библиотека предлагает для этого мощный и кроссплатформенный инструмент — std::chrono. Разберёмся, как им пользоваться безопасно и удобно на практике.

Какие часы выбрать: system_clock, steady_clock или high_resolution_clock

В <chrono> есть несколько «часов». Коротко о главных:

  • std::steady_clock — монотонные часы: не идут назад, не подвержены изменению системного времени. Выбор №1 для измерения длительностей.
  • std::system_clock — «настенные» часы: отражают реальное (календарное) время и могут скакать из-за изменения системного времени. Подходят для отметок времени, но не для измерения интервалов.
  • std::high_resolution_clock — максимально доступное разрешение. Часто это alias к одному из выше (зависит от реализации). Для кроссплатформенных измерений лучше предпочесть steady_clock.
  • Главное правило: не смешивайте разные часы при вычислении разницы (start и end должны быть из одного класса часов).

    Базовый пример: измерение времени выполнения функции

    #include <iostream>
    #include <chrono>
    #include <vector>
    #include <algorithm>
    
    using namespace std; 
    using namespace std::chrono;
    
    void heavy_task() {
        vector<int> v(2'000'000);
        iota(v.begin(), v.end(), 0);
        sort(v.begin(), v.end(), greater<>());
    }
    
    int main() {
        auto start = steady_clock::now();
        heavy_task();
        auto end = steady_clock::now();
    
        auto ms = duration_cast<milliseconds>(end - start).count();
        cout << "heavy_task заняла " << ms << " мсn";
    }
    

    Мы использовали steady_clock и перевели разницу во время в миллисекунды через duration_cast<milliseconds>. Для более точной печати в секундах — используйте duration<double>:

    auto sec = duration<double>(end - start).count();
    cout << fixed << setprecision(6)
         << "Время: " << sec << " сn";
    

    Удобные литералы и конвертации длительностей

    С C++14 появились литералы длительностей: ms, s, ns и т. д. Подключите их и пишите код выразительно:

    using namespace std::chrono_literals;
    
    auto d1 = 250ms;   // milliseconds
    auto d2 = 2s;      // seconds
    auto total = d1 + d2; // Сумма длительностей — OK
    
    cout << duration_cast<milliseconds>(total).count() << " мсn";
    

    Золотое правило: для вывода в дробных секундах делайте duration<double>, а не переводите в целые миллисекунды — иначе потеряете точность.

    Scope‑таймер: измеряем блок кода «автоматом»

    Небольшой класс, который замеряет время между созданием и уничтожением объекта. Удобно для быстрой диагностики (и безопасно при выходе по исключению).

    #include <string>
    
    class ScopeTimer {
    public:
        explicit ScopeTimer(string name)
            : name_(std::move(name)), start_(steady_clock::now()) {}
    
        ~ScopeTimer() {
            auto end = steady_clock::now();
            auto ms = duration_cast<milliseconds>(end - start_).count();
            cerr << name_ << ": " << ms << " мсn";
        }
    
    private:
        string name_;
        steady_clock::time_point start_;
    };
    
    int main() {
        ScopeTimer t("overall");
        heavy_task();
    }
    

    Советы: выводите в cerr (он не буферизуется как cout), или собирайте статистику и выводите позже. Для крупных проектов лучше внедрить логирование вместо прямого вывода с деструктора.

    Паузы и таймауты: sleep_for и sleep_until

    Для задержек используйте std::this_thread::sleep_for или sleep_until. Пример с литералами:

    #include <thread>
    
    int main() {
        using namespace std::chrono_literals;
        auto start = steady_clock::now();
        std::this_thread::sleep_for(150ms);
        auto end = steady_clock::now();
        cout << duration_cast<milliseconds>(end - start).count() << " мсn";
    }
    

    Для ожиданий и таймаутов в синхронизации применяйте steady_clock — системное время может измениться, а монотонные часы — нет:

    #include <mutex>
    #include <condition_variable>
    
    mutex mtx;
    condition_variable cv;
    bool ready = false;
    
    int main() {
        unique_lock<mutex> lk(mtx);
        auto deadline = steady_clock::now() + 500ms;
        bool ok = cv.wait_until(lk, deadline, []{ return ready; });
        cout << (ok ? "готово" : "таймаут") << 'n';
    }
    

    Частые ошибки при измерении времени в C++

  • Смешивание часов: start из system_clock, end из steady_clock — неопределённое поведение. Всегда используйте однотипные часы.
  • Измерение через system_clock: системное время может «скакать». Для длительности берите steady_clock.
  • Потеря точности: приведение к целым миллисекундам преждевременно. Для точных значений используйте duration<double> в секундах или миллисекундах.
  • Замер с выводом внутри измеряемого участка: I/O медленный и исказит результат. Выводите после замера или считайте среднее по многим прогоном.
  • Без «прогрева» и оптимизаций компилятора: включайте оптимизации (-O2/-O3), прогревайте код (несколько холостых прогонов), рассчитывайте медиану/перцентили, а не единичный замер.
  • Случайные перерывы ОС: на многозадачных системах шум неизбежен. Делайте серию измерений и усредняйте.
  • Небольшие утилиты для удобства

    template <class Rep, class Period>
    double to_seconds(chrono::duration<Rep, Period> d) {
        return chrono::duration<double>(d).count();
    }
    
    int main() {
        auto start = chrono::steady_clock::now();
        heavy_task();
        auto end = chrono::steady_clock::now();
    
        cout << fixed << setprecision(6)
             << "Секунд: " << to_seconds(end - start) << 'n';
    }
    

    Такой хелпер избавляет от лишних duration_cast и делает код чище и безопаснее для точных измерений.

    Чек‑лист: «измерение времени в C++» без ошибок

  • Для длительностей — std::steady_clock.
  • Не смешивайте типы часов.
  • Для вывода дробных значений используйте duration<double>.
  • Избегайте I/O внутри измеряемого участка, делайте серию прогонов.
  • Для задержек и таймаутов — sleep_for, sleep_until, wait_for, wait_until на steady_clock.
  • Что дальше изучить

    Если вы хотите системно прокачать основы C++ (включая работу со стандартной библиотекой, практику и проекты), посмотрите курс: Освоить C++ от нуля до уверенного уровня — курс «Программирование на C++ с Нуля до Гуру».

    Теперь у вас есть рабочий набор приёмов для «измерение времени в C++» на базе std::chrono. Применяйте их в бенчмаркинге, диагностике и грамотной работе с таймаутами — и ваши эксперименты станут точнее и воспроизводимее.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий