C++ std::chrono::steady_clock | Monotonic time for benchmarks

C++ std::chrono::steady_clock | Monotonic time for benchmarks

이 글의 핵심

steady_clock gives monotonic time_points for measuring intervals and deadlines without NTP jumps; when to prefer system_clock for wall time and how to read is_steady.

What is steady_clock?

A monotonically non-decreasing clock (C++11).

#include <chrono>

auto start = std::chrono::steady_clock::now();
// work
auto end = std::chrono::steady_clock::now();
auto elapsed = end - start;

Properties

// Monotonic increase guaranteed
// - Unaffected by system time changes
// - Always moves forward
// - Good for performance measurement

std::chrono::steady_clock::is_steady;  // true

Practical examples

Example 1: Benchmark helper

template<typename Func>
auto benchmark(Func f) {
    auto start = std::chrono::steady_clock::now();
    
    f();
    
    auto end = std::chrono::steady_clock::now();
    return std::chrono::duration_cast<std::chrono::microseconds>(
        end - start
    );
}

int main() {
    auto duration = benchmark([&] {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    });
    
    std::cout << "Elapsed: " << duration.count() << " μs" << std::endl;
}

Example 2: Timer class

class Timer {
    std::chrono::steady_clock::time_point start;
    
public:
    Timer() : start(std::chrono::steady_clock::now()) {}
    
    void reset() {
        start = std::chrono::steady_clock::now();
    }
    
    auto elapsed() const {
        auto end = std::chrono::steady_clock::now();
        return std::chrono::duration_cast<std::chrono::milliseconds>(
            end - start
        );
    }
};

int main() {
    Timer timer;
    
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    std::cout << "Elapsed: " << timer.elapsed().count() << " ms" << std::endl;
}

Example 3: Timeout polling

bool waitWithTimeout(std::chrono::milliseconds timeout) {
    auto start = std::chrono::steady_clock::now();
    
    while (true) {
        if (isReady()) {
            return true;
        }
        
        auto now = std::chrono::steady_clock::now();
        if (now - start >= timeout) {
            return false;  // timeout
        }
        
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

Example 4: Comparing algorithms

void comparePerformance() {
    auto duration1 = benchmark(algorithm1);
    auto duration2 = benchmark(algorithm2);
    
    if (duration1 < duration2) {
        std::cout << "algorithm1 is faster" << std::endl;
    }
    
    auto diff = duration2 - duration1;
    std::cout << "Delta: " << diff.count() << " μs" << std::endl;
}

system_clock vs steady_clock

// system_clock: wall time (can jump)
auto sys = std::chrono::system_clock::now();

// steady_clock: monotonic
auto steady = std::chrono::steady_clock::now();

// Prefer steady_clock for elapsed intervals

Common pitfalls

Pitfall 1: Wrong clock for intervals

// ❌ system_clock (user/NTP can move time backward)
auto start = std::chrono::system_clock::now();
// system time adjusted backward
auto end = std::chrono::system_clock::now();
// duration may be negative

// ✅ steady_clock
auto start = std::chrono::steady_clock::now();
auto end = std::chrono::steady_clock::now();
// non-negative for forward progress

Pitfall 2: Resolution

using period = std::chrono::steady_clock::period;
std::cout << "Resolution: " << period::num << "/" << period::den << " s" << std::endl;

Pitfall 3: Timer call overhead

auto start = std::chrono::steady_clock::now();
auto end = std::chrono::steady_clock::now();

auto overhead = end - start;
std::cout << "Overhead: " << overhead.count() << " ns" << std::endl;

Pitfall 4: Very long measurements

// For multi-day spans, watch representation limits (rare on desktop)
auto start = std::chrono::steady_clock::now();
// ...
auto end = std::chrono::steady_clock::now();

Usage patterns

// 1. Benchmark
auto duration = benchmark(func);

// 2. Timeout
bool success = waitWithTimeout(1000ms);

// 3. Compare algorithms
compareAlgorithms();

// 4. Profiling
profileFunction();

steady_clock vs system_clock vs high_resolution_clock

C++11 chrono offers three clocks you see most often. Picking “high_resolution” by name alone can hurt portability.

ClockMonotonic (is_steady)Meaningful absolute timeTypical use
steady_clockYesEpoch is implementation-defined; not great for “what time is it?”Elapsed time, deadlines, timeouts, benchmarks
system_clockNo (NTP/manual)Wall clock, works with to_time_tLog timestamps, file/DB times, calendar expiry
high_resolution_clockImplementation-definedOften alias of steady or systemShort micro-benchmarks (check is_steady)

Note on high_resolution_clock: The standard allows it to be a typedef of steady_clock or system_clock. For wall time, use system_clock; for elapsed/timeout, use steady_clock directly. Treat high_resolution_clock as a local micro-benchmark helper after you verify behavior.

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    std::cout << std::boolalpha;
    std::cout << "steady_clock::is_steady: " << steady_clock::is_steady << '\n';
    std::cout << "system_clock::is_steady: " << system_clock::is_steady << '\n';
    std::cout << "high_resolution_clock::is_steady: " << high_resolution_clock::is_steady << '\n';
}

Measurement patterns

  1. Single interval: auto t0 = steady_clock::now(); … work … auto dt = steady_clock::now() - t0; — keep native duration until you display, then duration_cast to reduce rounding drift.

  2. Accumulating segments: Sum duration values or differences of time_points on the same clock.

  3. Very short work: Two now() calls can already show hundreds of nanoseconds—calibrate or repeat and aggregate (mean/median).

  4. Mixed with sleep: sleep_for only guarantees a minimum sleep; measure real elapsed with steady_clock.

auto t0 = std::chrono::steady_clock::now();
do_work();
auto t1 = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0);

Benchmarking notes

  • Warm up caches and allocators before timing.
  • Repeat runs; use median or mean when noise is high.
  • Prevent optimization of dead work with benchmark helpers (DoNotOptimize) in real harnesses (e.g. Google Benchmark).
  • Clock choice: measure intervals with steady_clock; if you log human-readable timestamps, use system_clock only for that line.

The benchmark template above is illustrative; production code should pair micro-benchmarks with profiling tools.

Advanced: timeout with a deadline

For polling loops, a deadline time_point is often clearer than “start + budget” scattered everywhere.

#include <chrono>
#include <thread>

bool wait_until(std::chrono::steady_clock::time_point deadline,
                auto pred, std::chrono::milliseconds poll) {
    while (std::chrono::steady_clock::now() < deadline) {
        if (pred()) return true;
        std::this_thread::sleep_for(poll);
    }
    return false;
}
  • Prefer condition variables / futures when you can block efficiently instead of polling.
  • If deadline - steady_clock::now() is negative, the deadline already passed—clamp or check before casting.

Platform notes

  • Linux: steady_clock is usually monotonic (CLOCK_MONOTONIC-like). Check period for resolution.
  • Windows: MSVC/MinGW implementations vary but monotonicity is still the contract.
  • macOS/iOS: Typically monotonic; high_resolution_clock often aliases the same underlying clock.
  • Embedded/RTOS: Low tick rates can quantize short intervals—validate period and measurement error.
  • Very long uptime: Theoretical counter overflow is rare on desktop/server duration types.

One-line summary: Wall time → system_clock; durations and timeoutssteady_clock; do not pick high_resolution_clock by name alone—check is_steady and your implementation.

FAQ

Q1: What is steady_clock?

A: A clock whose time_points never decrease (for a given process run).

Q2: Difference from system_clock?

A: steady: monotonic. system: tracks system/wall time and can jump.

Q3: Performance measurement?

A: Prefer steady_clock for intervals.

Q4: Precision?

A: Platform-dependent; often nanosecond-class resolution.

Q5: Overhead of now()?

A: Usually small (nanoseconds) but not free for micro-benchmarks.

Q6: Learning resources?

A: C++ Primer, Effective Modern C++, cppreference — steady_clock.


  • C++ Chrono overview
  • C++ Chrono guide
  • C++ time_point

Practical tips

Debugging

  • Fix compiler warnings first.
  • Reproduce with a small test.

Performance

  • Profile before optimizing.
  • Define measurable targets.

Code review

  • Follow team conventions.

Production checklist

Before coding

  • Right approach?
  • Maintainable?
  • Meets performance needs?

While coding

  • Warnings cleared?
  • Edge cases covered?
  • Error handling OK?

At review

  • Intent clear?
  • Tests sufficient?
  • Documented?

Keywords

C++, steady_clock, chrono, benchmark, C++11


  • C++ Chrono guide |
  • C++ Chrono |
  • C++ duration |
  • C++ ratio |
  • C++ Stopwatch and benchmark