본문 바로가기

프로그래밍 강좌/C++

[C++] 함수 실행 시간 측정 방법 - chrono 총정리 duration_cast system_clock getTickCount

지난번에 네이버 블로그를 통해 현재 시간 얻는 방법에 대해 언급하면서 chrono에 대해 잠시 알아봤습니다.

현재 시간을 알아낼 수 있다는 것어떠한 함수 시작 부분에서의 시간을 얻어오고 함수의 끝 부분의 시간을 얻어온 후

두 시간의 차이를 측정하면 그 함수(이벤트)의 수행 시간을 알 수 있겠죠.

 

chrono 클래스란?

 

chrono C++11 에서 추가된 시간 관련된 라이브러리입니다.

clock, getTickCount, timeGetTime 어떠한 함수(이벤트)의 수행 시간을 측정하는 방법들이 많이 있지만 정밀도 사용성 측면에서 chrono 를 사용하는 것을 추천합니다.

정밀도는 nano second 까지 지원하고, 시간 단위의 값 끼리 연산도 가능합니다.

 

chrono 사용법

chrono를 사용하기 위해서 #include <chrono>를 추가해 주어야 합니다.

가장 기본적으로 foo()라는 함수의 수행 시간을 측정하는 소스는 아래와 같습니다.

#include <iostream>
#include <chrono>

void foo()
{
    int sum = 0;
    for (int i = 0; i < 10000000; i++) {
        sum += i;
    }
}

int main()
{
    std::chrono::system_clock::time_point start = std::chrono::system_clock::now();

    foo();

    std::chrono::system_clock::time_point end = std::chrono::system_clock::now();

    std::chrono::duration<double, std::milli> msec = end - start;    
    std::cout << "실행시간: " << msec.count() << " msec" << std::endl;

    return 0;
}
 

time_point 시간상의 하나의 포인트를 의미하고 now() 함수를 통해 그 시점의 시간을 마킹한다라고 생각하면 됩니다.

경과 시간을 아래와 같은 단위로 나타낼 수 있습니다.

std::chrono::nanoseconds   // 나노 세컨드
std::chrono::microseconds  // 마이크로 세컨드
std::chrono::milliseconds  // 밀리 세컨드
std::chrono::seconds       // 초
std::chrono::minutes       // 분
std::chrono::hours         // 시
 

chrono 의 now()는 기본이 nanoseconds 로 반환하기 때문에 nano 초일 때에는 바로 가능하지만, 나머지 단위를 사용하려면 아래와 같이 chrono에서 제공하는 duration_cast< >를 사용해야 합니다.

std::chrono::nanoseconds nano = end - start;
std::chrono::microseconds micro = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::chrono::milliseconds milli = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::chrono::seconds sec = std::chrono::duration_cast<std::chrono::seconds>(end - start);
std::chrono::minutes min = std::chrono::duration_cast<std::chrono::minutes>(end - start);
std::chrono::hours hour = std::chrono::duration_cast<std::chrono::hours>(end - start);
 

이걸 이용해서 한번 측정 결과를 print 해 볼까요?

#include <iostream>
#include <chrono>

void foo()
{
    int sum = 0;
    for (int i = 0; i < 10000000; i++) {
        sum += i;
    }
}

int main()
{
    std::chrono::system_clock::time_point start = std::chrono::system_clock::now();

    foo();

    std::chrono::system_clock::time_point end = std::chrono::system_clock::now();

    std::chrono::nanoseconds nano = end - start;
    std::chrono::microseconds micro = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    std::chrono::milliseconds milli = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::chrono::seconds sec = std::chrono::duration_cast<std::chrono::seconds>(end - start);
    std::chrono::minutes min = std::chrono::duration_cast<std::chrono::minutes>(end - start);
    std::chrono::hours hour = std::chrono::duration_cast<std::chrono::hours>(end - start);
    
    std::cout << "실행시간(nanoseconds):" << nano.count() << " nsec" << std::endl;
    std::cout << "실행시간(microseconds):" << micro.count() << " usec" << std::endl;
    std::cout << "실행시간(milliseconds):" << milli.count() << " msec" << std::endl;
    std::cout << "실행시간(seconds):" << sec.count() << " sec" << std::endl;
    std::cout << "실행시간(minutes):" << min.count() << " min" << std::endl;
    std::cout << "실행시간(hours):" << hour.count() << " hour" << std::endl;

    return 0;
}
 

위와 같이 프린트 해 보면, 결과는 아래와 같아요.

아. 그런데 좀 소수점 자리가 짤리는게 좀 아쉽죠?

std::chrono::duration<double, std::micro> micro = end - start;
std::chrono::duration<double, std::milli> milli = end - start;
std::chrono::duration<double> sec = end - start;
std::chrono::duration<double, std::ratio<60>> min = end - start;
std::chrono::duration<double, std::ratio<3600>> hour = end - start;
 

위와 같이 duration을 이용한 템플릿을 사용하면 소수점 표현이 가능합니다.

전체 소스를 보면,

#include <iostream>
#include <chrono>

void foo()
{
    int sum = 0;
    for (int i = 0; i < 10000000; i++) {
        sum += i;
    }
}

int main()
{
    std::chrono::system_clock::time_point start = std::chrono::system_clock::now();

    foo();

    std::chrono::system_clock::time_point end = std::chrono::system_clock::now();

    std::chrono::nanoseconds nano = end - start;
    std::chrono::duration<double, std::micro> micro = end - start;
    std::chrono::duration<double, std::milli> milli = end - start;
    std::chrono::duration<double> sec = end - start;
    std::chrono::duration<double, std::ratio<60>> min = end - start;
    std::chrono::duration<double, std::ratio<3600>> hour = end - start;
    
    std::cout << "실행시간(nanoseconds):" << nano.count() << " nsec" << std::endl;
    std::cout << "실행시간(microseconds):" << micro.count() << " usec" << std::endl;
    std::cout << "실행시간(milliseconds):" << milli.count() << " msec" << std::endl;
    std::cout << "실행시간(seconds):" << sec.count() << " sec" << std::endl;
    std::cout << "실행시간(minutes):" << min.count() << " min" << std::endl;
    std::cout << "실행시간(hours):" << hour.count() << " hour" << std::endl;

    return 0;
}
 

위와 같이 되고, 결과는 아래와 같습니다.

 

clock 클래스들

chrono에는 정말 많은 clock들이 있지만, system_clock, steady_clock 정도만 알아두시면 됩니다.

1. system_clock

가장 일반적인 clock 클래스입니다.

이는 time_t와 호환성이 있어서 아래와 같이 사용할수 있습니다.

// time_point -> time_t
std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();
time_t curTime = std::chrono::system_clock::to_time_t(tp);
    
std::cout << "1970년으로부터 몇 초 지났나요?:" << curTime << " 초" << std::endl;

// time_t -> time_point
time_t currTime = time(nullptr);
std::chrono::system_clock::time_point curTp = std::chrono::system_clock::from_time_t(currTime);
 

steady_clock은 system_clock을 상속 받으면서 2개의 정적 변수를 추가로 가지고 있습니다.

steady_clock은 절대 거꾸로 가는 일이 없으므로 monotonic_clock이라는 별명이 있어요. 아래와 같이 system_clock과 똑같아요.

system_clock steady_clock으로 바꾸면 끝~!!

#include <iostream>
#include <chrono>

void foo()
{
    int sum = 0;
    for (int i = 0; i < 10000000; i++) {
        sum += i;
    }
}

int main()
{
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();

    foo();

    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();

    std::chrono::nanoseconds nano = end - start;
    std::cout << "실행시간(nanoseconds):" << nano.count() << " nsec" << std::endl;
    
    return 0;
}
 

여기서 그냥 끝내면 제가 아쉽죠?

 

chrono의 부하는 어느정도일까요?

프로그램이 수행되는 동안 chrono를 통해 함수 실행 속도를 측정하는데, 이게 얼마만큼의 시간을 소모할까요?

for문을 통해 100,000회 now()를 불러봤는데요.

#include <iostream>
#include <chrono>

void foo()
{
    int sum = 0;
    std::chrono::steady_clock::time_point start;
    for (int i = 0; i < 100000; i++) {
        start = std::chrono::steady_clock::now();
    }
}

int main()
{
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();

    foo();

    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();

    std::chrono::duration<double, std::milli> milli = end - start;
    
    std::cout << "실행시간(milliseconds):" << milli.count() << " msec" << std::endl;
    
    return 0;
}
 

결과는 겨우 2.4 msec 입니다. 별 무리 없겠죠?

그럼 정말 꿀팁 하나를 더 투척하자면, 여러 함수가 있을 때 매번 chrono를 쓰기 귀찮죠? 소스도 복잡해지고.

 

매크로 이용하여 chrono 쉽게 사용하기

우리에겐 매크로라는 게 있어요.

아래와 같이 매크로를 만들어서 쓰면,

#define _MEASURE_START(STR)	std::chrono::steady_clock::time_point start_##STR = std::chrono::steady_clock::now();
#define _MEASURE_END(STR)	std::chrono::duration<double, std::milli> duration_msec_##STR = std::chrono::steady_clock::now() - start_##STR;\
							std::cout << ">>>>> [" << #STR << "]\t" << duration_msec_##STR.count() << " msec\n";
 

여러 함수의 실행 속도를 쉽게 print 해 볼 수 있습니다.

좀만 수정하면 파일로도 저장할 수 있고, 다양하게 응용하실 수 있겠죠?

#include <iostream>
#include <chrono>

#define _MEASURE_START(STR)	std::chrono::steady_clock::time_point start_##STR = std::chrono::steady_clock::now();
#define _MEASURE_END(STR)	std::chrono::duration<double, std::milli> duration_msec_##STR = std::chrono::steady_clock::now() - start_##STR;\
							std::cout << ">>>>> [" << #STR << "]\t" << duration_msec_##STR.count() << " msec\n";

void foo()
{    
    std::chrono::steady_clock::time_point start;
    for (int i = 0; i < 100000; i++) {
        start = std::chrono::steady_clock::now();
    }
}

void goo()
{
    int sum = 0;
    for (int i = 0; i < 1000000; i++) {
        sum += i;
    }
}

int main()
{
    _MEASURE_START(foo);
    foo();
    _MEASURE_END(foo);

    _MEASURE_START(goo);
    goo();
    _MEASURE_END(goo);    
    
    return 0;
}
 

속도는 각자가 사용하는 컴퓨터 사양에 따라, Release, Debug 모드에 따라 달라질꺼에요.