Skip to Content
Course content

201: Working with Dates and Times: chrono

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Let's say we've written a function that handles some heavy lifting—maybe a complex calculation or a database query—and it feels slow. Your first instinct is probably to time it. In the old days, we'd just use time() from <ctime>, but that only gives us resolution in seconds. In modern software, a second is an eternity. We need something more granular.

The frustration with second-level precision

I'll start with a simple loop that simulates some work. I'm going to try and time it using the old-school approach just to show you why we've moved on. Watch what happens when I run this:

#include <iostream>
#include <ctime>
#include <vector>

void do_work() {
    std::vector<int> v;
    for(int i = 0; i < 1000000; ++i) v.push_back(i);
}

int main() {
    std::time_t start = std::time(nullptr);
    do_work();
    std::time_t end = std::time(nullptr);
    std::cout < "Elapsed time: " < (end - start) < " seconds\n";
    return 0;
}

If you run this, you'll almost certainly see "Elapsed time: 0 seconds." The function is fast, but not that fast. It's taking maybe 20 or 30 milliseconds, but because std::time_t only cares about whole seconds, it rounds down. This is where <chrono> comes in. It's a bit more verbose, but it's infinitely more precise.

Capturing the start and end

Now, I'll swap that out for std::chrono. I'm going to use steady_clock. I prefer this over system_clock for timing because the system clock can be adjusted by the OS or a network time sync while your program is running, which would totally wreck your measurements. steady_clock is like a stopwatch; it only goes forward.

#include <iostream>
#include <chrono>
#include <vector>

void do_work() {
    std::vector<int> v;
    for(int i = 0; i < 1000000; ++i) v.push_back(i);
}

int main() {
    auto start = std::chrono::steady_clock::now();
    do_work();
    auto end = std::chrono::steady_clock::now();
    
    // What do we have here?
    // std::cout < (end - start) < "\n"; // This won't compile!
}

I used auto because the actual type of now() is std::chrono::time_point<std::chrono::steady_clock>. Writing that out every time is a nightmare. But notice my commented-out line. If I try to print the result of end - start, the compiler screams at me. Why? Because the result isn't an integer or a float; it's a std::chrono::duration object.

Dealing with the duration type

C++ is being strict here on purpose. A "duration" doesn't have a unit until you tell it what unit you want. Is it 50 milliseconds? 50,000 microseconds? The duration object keeps the value in "ticks," but it doesn't know how you want to display it. This prevents the classic bug where one developer assumes a function returns seconds and another assumes it returns milliseconds.

To get a number I can actually use, I need to "cast" this duration into a specific unit using duration_cast. Let's try to get the result in milliseconds:

auto diff = end - start;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(diff);
std::cout < "Elapsed time: " < ms.count() < "ms\n";

The .count() method is the final step—it strips away the chrono wrapper and gives us the raw numerical value. Now we're getting somewhere. I can see exactly how many milliseconds my vector allocation took.

Picking the right granularity

Wait, what if milliseconds is still too coarse? If I'm timing a single mathematical operation, 1ms is huge. I can just swap the template argument. If I change std::chrono::milliseconds to std::chrono::microseconds or std::chrono::nanoseconds, the logic remains identical. The beauty of this system is that the conversion is handled safely by the library.

Just a quick heads-up: be careful with duration_cast. It performs integer truncation. If your code took 1.9 milliseconds and you cast it to milliseconds, you'll get 1, not 2. If you need floating-point precision, you can define a custom duration using std::chrono::duration<double>, but for most profiling tasks, microseconds are plenty.




📋 Practical Task

Build a High-Resolution Execution Timer for a Prime Number Search

Your task is to create a program that finds the largest prime number smaller than 100,000. However, the focus isn't on the math, but on the measurement.

  • Implement a simple function bool isPrime(int n).
  • Write a loop in main that iterates backward from 100,000 to find the first prime number.
  • Use std::chrono::steady_clock to measure exactly how long that search takes.
  • Print the result first in milliseconds and then in microseconds.
  • Ensure you use std::chrono::duration_cast to convert the time points into these specific units.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.