Programing

C ++에서 코드 스 니펫의 실행 시간을 계산하는 방법

lottogame 2020. 7. 17. 08:11
반응형

C ++에서 코드 스 니펫의 실행 시간을 계산하는 방법


C ++ 코드 스 니펫의 실행 시간을 초 단위로 계산해야합니다. Windows 또는 Unix 시스템에서 작동해야합니다.

이 작업을 수행하려면 다음 코드를 사용하십시오. (이전 가져 오기)

clock_t startTime = clock();
// some code here
// to compute its execution duration in runtime
cout << double( clock() - startTime ) / (double)CLOCKS_PER_SEC<< " seconds." << endl;

그러나 작은 입력 또는 a = a + 1과 같은 짧은 문장의 경우 "0 초"결과가 나타납니다. 0.0000001 초 또는 그와 비슷한 것이어야한다고 생각합니다.

System.nanoTime()이 경우 Java에서 꽤 잘 작동 한다는 것을 기억합니다 . 그러나 clock()C ++의 기능에서 동일한 기능을 정확히 얻을 수는 없습니다 .

해결책이 있습니까?


내가 쓴이 기능을 사용할 수 있습니다. 을 호출 GetTimeMs64()하면 시스템 시계를 사용하여 유닉스 시대 이후 경과 된 밀리 초 수를 반환합니다 (밀리 초 time(NULL)제외).

윈도우와 리눅스 모두에서 작동합니다. 스레드 안전합니다.

입도는 창에서 15ms입니다. 리눅스에서는 구현에 따라 다르지만 일반적으로 15ms입니다.

#ifdef _WIN32
#include <Windows.h>
#else
#include <sys/time.h>
#include <ctime>
#endif

/* Remove if already defined */
typedef long long int64; typedef unsigned long long uint64;

/* Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both
 * windows and linux. */

uint64 GetTimeMs64()
{
#ifdef _WIN32
 /* Windows */
 FILETIME ft;
 LARGE_INTEGER li;

 /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it
  * to a LARGE_INTEGER structure. */
 GetSystemTimeAsFileTime(&ft);
 li.LowPart = ft.dwLowDateTime;
 li.HighPart = ft.dwHighDateTime;

 uint64 ret = li.QuadPart;
 ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */
 ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */

 return ret;
#else
 /* Linux */
 struct timeval tv;

 gettimeofday(&tv, NULL);

 uint64 ret = tv.tv_usec;
 /* Convert from micro seconds (10^-6) to milliseconds (10^-3) */
 ret /= 1000;

 /* Adds the seconds (10^0) after converting them to milliseconds (10^-3) */
 ret += (tv.tv_sec * 1000);

 return ret;
#endif
}

마이크로 초 (UNIX, POSIX 등)를 사용하는 다른 작업 예제가 있습니다.

    #include <sys/time.h>
    typedef unsigned long long timestamp_t;

    static timestamp_t
    get_timestamp ()
    {
      struct timeval now;
      gettimeofday (&now, NULL);
      return  now.tv_usec + (timestamp_t)now.tv_sec * 1000000;
    }

    ...
    timestamp_t t0 = get_timestamp();
    // Process
    timestamp_t t1 = get_timestamp();

    double secs = (t1 - t0) / 1000000.0L;

이것을 코딩 한 파일은 다음과 같습니다.

https://github.com/arhuaco/junkcode/blob/master/emqbit-bench/bench.c


다음은 C ++ 11의 간단한 솔루션으로 만족스러운 해상도를 제공합니다.

#include <iostream>
#include <chrono>

class Timer
{
public:
    Timer() : beg_(clock_::now()) {}
    void reset() { beg_ = clock_::now(); }
    double elapsed() const { 
        return std::chrono::duration_cast<second_>
            (clock_::now() - beg_).count(); }

private:
    typedef std::chrono::high_resolution_clock clock_;
    typedef std::chrono::duration<double, std::ratio<1> > second_;
    std::chrono::time_point<clock_> beg_;
};

또는 c ++ 03의 경우 * nix에서

#include <iostream>
#include <ctime>

class Timer
{
public:
    Timer() { clock_gettime(CLOCK_REALTIME, &beg_); }

    double elapsed() {
        clock_gettime(CLOCK_REALTIME, &end_);
        return end_.tv_sec - beg_.tv_sec +
            (end_.tv_nsec - beg_.tv_nsec) / 1000000000.;
    }

    void reset() { clock_gettime(CLOCK_REALTIME, &beg_); }

private:
    timespec beg_, end_;
};

사용법 예는 다음과 같습니다.

int main()
{
    Timer tmr;
    double t = tmr.elapsed();
    std::cout << t << std::endl;

    tmr.reset();
    t = tmr.elapsed();
    std::cout << t << std::endl;

    return 0;
}

에서 https://gist.github.com/gongzhitaao/7062087


#include <boost/progress.hpp>

using namespace boost;

int main (int argc, const char * argv[])
{
  progress_timer timer;

  // do stuff, preferably in a 100x loop to make it take longer.

  return 0;
}

progress_timer범위를 벗어나 그것의 창조 이후 경과 된 시간을 인쇄합니다.

업데이트 : 간단한 독립형 교체 (OSX / iOS이지만 이식하기 쉬운)를 만들었습니다 : https://github.com/catnapgames/TestTimerScoped


Windows는 QueryPerformanceCounter () 함수를 제공하고 Unix에는 gettimeofday ()가 있습니다. 두 함수 모두 최소 1 마이크로 초 차이를 측정 할 수 있습니다.


일부 프로그램에서 나는 그런 목적으로 RDTS사용 했다고 썼다 . RDTSC는 시간이 아니라 프로세서 시작 후의주기 수입니다. 결과를 두 번째로 얻으려면 시스템에서 교정해야하지만 성능을 평가하려고 할 때 실제로 편리합니다. 다시 몇 초로 변경하지 않고 직접 사이클 수를 사용하는 것이 좋습니다.

(위의 링크는 프랑스어 위키 백과 페이지에 있지만 C ++ 코드 샘플이 있으며 영어 버전은 여기에 있습니다 )


시스템에서 시간 정보를 얻기 위해 표준 라이브러리 함수를 사용하는 것이 좋습니다.

더 나은 해상도를 원하면 더 많은 실행 반복을 수행하십시오. 프로그램을 한 번 실행하고 샘플을 얻는 대신 1000 번 이상 실행하십시오.


전체 루프 (성능 + 성능 타이밍)를 여러 번 실행하는 것보다 내부 루프 반복을 나누어서 성능 타이밍으로 한 번만 내부 루프를 여러 번 실행하는 것이 좋습니다. 이렇게하면 실제 타이밍 섹션에 비해 성능 타이밍 코드의 오버 헤드가 줄어 듭니다.

적절한 시스템에 대한 타이머 호출을 래핑하십시오. Windows의 경우 QueryPerformanceCounter는 사용하기 매우 빠르고 안전합니다.

최신 X86 PC에서도 "rdtsc"를 사용할 수 있지만 일부 멀티 코어 컴퓨터 (코어 호핑 타이머가 변경 될 수 있음) 또는 속도 단계가 설정된 경우 문제가있을 수 있습니다.


(Windows 전용 솔루션) Windows에서 정확한 타이밍을 얻는 현재 (2017 년경) 방법은 "QueryPerformanceCounter"를 사용하는 것입니다. 이 방법은 매우 정확한 결과를 얻을 수 있다는 이점이 있으며 MS에서 권장합니다. 코드 블롭을 새로운 콘솔 앱에 집어 넣어 작동 샘플을 얻으십시오. 여기에는 긴 토론이 있습니다 : 고해상도 타임 스탬프 획득

#include <iostream>
#include <tchar.h>
#include <windows.h>

int main()
{
constexpr int MAX_ITER{ 10000 };
constexpr __int64 us_per_hour{ 3600000000ull }; // 3.6e+09
constexpr __int64 us_per_min{ 60000000ull };
constexpr __int64 us_per_sec{ 1000000ull };
constexpr __int64 us_per_ms{ 1000ull };

// easy to work with
__int64 startTick, endTick, ticksPerSecond, totalTicks = 0ull;

QueryPerformanceFrequency((LARGE_INTEGER *)&ticksPerSecond);

for (int iter = 0; iter < MAX_ITER; ++iter) {// start looping
    QueryPerformanceCounter((LARGE_INTEGER *)&startTick); // Get start tick
    // code to be timed
    std::cout << "cur_tick = " << iter << "\n";
    QueryPerformanceCounter((LARGE_INTEGER *)&endTick); // Get end tick
    totalTicks += endTick - startTick; // accumulate time taken
}

// convert to elapsed microseconds
__int64 totalMicroSeconds =  (totalTicks * 1000000ull)/ ticksPerSecond;

__int64 hours = totalMicroSeconds / us_per_hour;
totalMicroSeconds %= us_per_hour;
__int64 minutes = totalMicroSeconds / us_per_min;
totalMicroSeconds %= us_per_min;
__int64 seconds = totalMicroSeconds / us_per_sec;
totalMicroSeconds %= us_per_sec;
__int64 milliseconds = totalMicroSeconds / us_per_ms;
totalMicroSeconds %= us_per_ms;


std::cout << "Total time: " << hours << "h ";
std::cout << minutes << "m " << seconds << "s " << milliseconds << "ms ";
std::cout << totalMicroSeconds << "us\n";

return 0;
}

각 테스트마다 정확히 동일한 시간을 생성해야하는 스레드 스케줄링에 대한 완벽한 솔루션은 OS 독립적으로 프로그램을 컴파일하고 OS가없는 환경에서 프로그램을 실행하도록 컴퓨터를 부팅하는 것입니다. 그러나 이것은 대체로 비현실적이며 기껏해야 어렵다. OS를 사용하지 않는 것이 좋은 방법은 현재 스레드의 선호도를 1 코어로 설정하고 우선 순위를 가장 높게 설정하는 것입니다. 이 대안은 일관되고 충분한 결과를 제공해야합니다. 또한 디버깅을 방해하는 최적화를 해제해야합니다 .g ++ 또는 gcc의 경우 명령 줄에 추가 하는 것을 의미 합니다.-Og테스트중인 코드가 최적화되지 않도록합니다. -O0 플래그는 타이밍 결과에 포함될 불필요한 불필요한 오버 헤드를 발생시켜 코드의 시간 제한 속도를 왜곡 시키므로 사용해서는 안됩니다. 반대로, 최종 프로덕션 빌드 -Ofast에서 (또는 최소한 -O3) 사용한다고 가정하고 "죽은"코드 제거 문제를 무시하는 경우 -Og와 비교하여 최적화가 거의 수행되지 않습니다 -Ofast. 따라서 -Og최종 제품에서 코드의 실제 속도를 잘못 나타낼 수 있습니다. 또한, 모든 속도 테스트 (어느 정도까지)가 다음과 같이 왜곡됩니다.-Ofast코드의 각 스 니펫 / 섹션 / 기능은 분리되지 않습니다. 대신, 각 코드 스 니펫은 계속해서 다음 코드로 유입되므로 컴파일러는 잠재적으로 모든 코드 조각을 잠재적으로 결합, 병합 및 최적화 할 수 있습니다. 동시에을 많이 사용하는 코드 스 니펫을 벤치마킹하는 경우 realloc메모리 조각화가 충분한 프로덕션 제품에서 코드 스 니펫이 느려질 수 있습니다. 따라서 최종 프로덕션 빌드의 코드가 속도 테스트중인 개별 스 니펫보다 현저히 빠르거나 느리게 실행될 수 있기 때문에 "전체가 부분의 합보다 더 많다"는 표현이이 상황에 적용됩니다. 부적합을 줄일 수있는 부분 솔루션은 -Ofast다음을 추가 하여 속도 테스트에 사용 하고 있습니다.asm volatile("" :: "r"(var)) 데드 코드 / 루프 제거를 방지하기 위해 테스트에 포함 된 변수

다음은 Windows 컴퓨터에서 제곱근 기능을 벤치마킹하는 방법에 대한 예입니다.

// set USE_ASM_TO_PREVENT_ELIMINATION  to 0 to prevent `asm volatile("" :: "r"(var))`
// set USE_ASM_TO_PREVENT_ELIMINATION  to 1 to enforce `asm volatile("" :: "r"(var))`
#define USE_ASM_TO_PREVENT_ELIMINATION 1

#include <iostream>
#include <iomanip>
#include <cstdio>
#include <chrono>
#include <cmath>
#include <windows.h>
#include <intrin.h>
#pragma intrinsic(__rdtsc)
#include <cstdint>

class Timer {
public:
    Timer() : beg_(clock_::now()) {}
    void reset() { beg_ = clock_::now(); }
    double elapsed() const { 
        return std::chrono::duration_cast<second_>
            (clock_::now() - beg_).count(); }
private:
    typedef std::chrono::high_resolution_clock clock_;
    typedef std::chrono::duration<double, std::ratio<1> > second_;
    std::chrono::time_point<clock_> beg_;
};

unsigned int guess_sqrt32(register unsigned int n) {
    register unsigned int g = 0x8000;
    if(g*g > n) {
        g ^= 0x8000;
    }
    g |= 0x4000;
    if(g*g > n) {
        g ^= 0x4000;
    }
    g |= 0x2000;
    if(g*g > n) {
        g ^= 0x2000;
    }
    g |= 0x1000;
    if(g*g > n) {
        g ^= 0x1000;
    }
    g |= 0x0800;
    if(g*g > n) {
        g ^= 0x0800;
    }
    g |= 0x0400;
    if(g*g > n) {
        g ^= 0x0400;
    }
    g |= 0x0200;
    if(g*g > n) {
        g ^= 0x0200;
    }
    g |= 0x0100;
    if(g*g > n) {
        g ^= 0x0100;
    }
    g |= 0x0080;
    if(g*g > n) {
        g ^= 0x0080;
    }
    g |= 0x0040;
    if(g*g > n) {
        g ^= 0x0040;
    }
    g |= 0x0020;
    if(g*g > n) {
        g ^= 0x0020;
    }
    g |= 0x0010;
    if(g*g > n) {
        g ^= 0x0010;
    }
    g |= 0x0008;
    if(g*g > n) {
        g ^= 0x0008;
    }
    g |= 0x0004;
    if(g*g > n) {
        g ^= 0x0004;
    }
    g |= 0x0002;
    if(g*g > n) {
        g ^= 0x0002;
    }
    g |= 0x0001;
    if(g*g > n) {
        g ^= 0x0001;
    }
    return g;
}

unsigned int empty_function( unsigned int _input ) {
    return _input;
}

unsigned long long empty_ticks=0;
double empty_seconds=0;
Timer my_time;

template<unsigned int benchmark_repetitions>
void benchmark( char* function_name, auto (*function_to_do)( auto ) ) {
    register unsigned int i=benchmark_repetitions;
    register unsigned long long start=0;
    my_time.reset();
    start=__rdtsc();
    while ( i-- ) {
        auto result = (*function_to_do)( i << 7 );
        #if USE_ASM_TO_PREVENT_ELIMINATION == 1
            asm volatile("" :: "r"(
                // There is no data type in C++ that is smaller than a char, so it will
                //  not throw a segmentation fault error to reinterpret any arbitrary
                //  data type as a char. Although, the compiler might not like it.
                result
            ));
        #endif
    }
    if ( function_name == nullptr ) {
        empty_ticks = (__rdtsc()-start);
        empty_seconds = my_time.elapsed();
        std::cout<< "Empty:\n" << empty_ticks
              << " ticks\n" << benchmark_repetitions << " repetitions\n"
               << std::setprecision(15) << empty_seconds
                << " seconds\n\n";
    } else {
        std::cout<< function_name<<":\n" << (__rdtsc()-start-empty_ticks)
              << " ticks\n" << benchmark_repetitions << " repetitions\n"
               << std::setprecision(15) << (my_time.elapsed()-empty_seconds)
                << " seconds\n\n";
    }
}


int main( void ) {
    void* Cur_Thread=   GetCurrentThread();
    void* Cur_Process=  GetCurrentProcess();
    unsigned long long  Current_Affinity;
    unsigned long long  System_Affinity;
    unsigned long long furthest_affinity;
    unsigned long long nearest_affinity;

    if( ! SetThreadPriority(Cur_Thread,THREAD_PRIORITY_TIME_CRITICAL) ) {
        SetThreadPriority( Cur_Thread, THREAD_PRIORITY_HIGHEST );
    }
    if( ! SetPriorityClass(Cur_Process,REALTIME_PRIORITY_CLASS) ) {
        SetPriorityClass( Cur_Process, HIGH_PRIORITY_CLASS );
    }
    GetProcessAffinityMask( Cur_Process, &Current_Affinity, &System_Affinity );
    furthest_affinity = 0x8000000000000000ULL>>__builtin_clzll(Current_Affinity);
    nearest_affinity  = 0x0000000000000001ULL<<__builtin_ctzll(Current_Affinity);
    SetProcessAffinityMask( Cur_Process, furthest_affinity );
    SetThreadAffinityMask( Cur_Thread, furthest_affinity );

    const int repetitions=524288;

    benchmark<repetitions>( nullptr, empty_function );
    benchmark<repetitions>( "Standard Square Root", standard_sqrt );
    benchmark<repetitions>( "Original Guess Square Root", original_guess_sqrt32 );
    benchmark<repetitions>( "New Guess Square Root", new_guess_sqrt32 );


    SetThreadPriority( Cur_Thread, THREAD_PRIORITY_IDLE );
    SetPriorityClass( Cur_Process, IDLE_PRIORITY_CLASS );
    SetProcessAffinityMask( Cur_Process, nearest_affinity );
    SetThreadAffinityMask( Cur_Thread, nearest_affinity );
    for (;;) { getchar(); }

    return 0;
}

또한 타이머로 Mike Jarvis에게 감사의 뜻을 전합니다.

더 큰 코드 스 니펫을 실행하려면 컴퓨터가 정지되는 것을 방지하기 위해 반복 횟수를 줄여야한다는 점에 유의하십시오 (매우 중요합니다).


실행될 때마다 동일한 코드를 시간을 측정하려는 경우 (예 : 병목 현상이 될 수있는 코드 프로파일 링) Andreas Bonini의 기능에 대한 래퍼는 다음과 같습니다.

#ifdef _WIN32
#include <Windows.h>
#else
#include <sys/time.h>
#endif

/*
 *  A simple timer class to see how long a piece of code takes. 
 *  Usage:
 *
 *  {
 *      static Timer timer("name");
 *
 *      ...
 *
 *      timer.start()
 *      [ The code you want timed ]
 *      timer.stop()
 *
 *      ...
 *  }
 *
 *  At the end of execution, you will get output:
 *
 *  Time for name: XXX seconds
 */
class Timer
{
public:
    Timer(std::string name, bool start_running=false) : 
        _name(name), _accum(0), _running(false)
    {
        if (start_running) start();
    }

    ~Timer() { stop(); report(); }

    void start() {
        if (!_running) {
            _start_time = GetTimeMicroseconds();
            _running = true;
        }
    }
    void stop() {
        if (_running) {
            unsigned long long stop_time = GetTimeMicroseconds();
            _accum += stop_time - _start_time;
            _running = false;
        }
    }
    void report() { 
        std::cout<<"Time for "<<_name<<": " << _accum / 1.e6 << " seconds\n"; 
    }
private:
    // cf. http://stackoverflow.com/questions/1861294/how-to-calculate-execution-time-of-a-code-snippet-in-c
    unsigned long long GetTimeMicroseconds()
    {
#ifdef _WIN32
        /* Windows */
        FILETIME ft;
        LARGE_INTEGER li;

        /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it
         *   * to a LARGE_INTEGER structure. */
        GetSystemTimeAsFileTime(&ft);
        li.LowPart = ft.dwLowDateTime;
        li.HighPart = ft.dwHighDateTime;

        unsigned long long ret = li.QuadPart;
        ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */
        ret /= 10; /* From 100 nano seconds (10^-7) to 1 microsecond (10^-6) intervals */
#else
        /* Linux */
        struct timeval tv;

        gettimeofday(&tv, NULL);

        unsigned long long ret = tv.tv_usec;
        /* Adds the seconds (10^0) after converting them to microseconds (10^-6) */
        ret += (tv.tv_sec * 1000000);
#endif
        return ret;
    }
    std::string _name;
    long long _accum;
    unsigned long long _start_time;
    bool _running;
};

코드 블록을 벤치마킹하는 간단한 클래스 :

using namespace std::chrono;

class benchmark {
  public:
  time_point<high_resolution_clock>  t0, t1;
  unsigned int *d;
  benchmark(unsigned int *res) : d(res) { 
                 t0 = high_resolution_clock::now();
  }
  ~benchmark() { t1 = high_resolution_clock::now();
                  milliseconds dur = duration_cast<milliseconds>(t1 - t0);
                  *d = dur.count();
  }
};
// simple usage 
// unsigned int t;
// { // put the code in a block
//  benchmark bench(&t);
//  // ...
//  // code to benchmark
// }
// HERE the t contains time in milliseconds

// one way to use it can be :
#define BENCH(TITLE,CODEBLOCK) \
  unsigned int __time__##__LINE__ = 0;  \
  { benchmark bench(&__time__##__LINE__); \
      CODEBLOCK \
  } \
  printf("%s took %d ms\n",(TITLE),__time__##__LINE__);


int main(void) {
  BENCH("TITLE",{
    for(int n = 0; n < testcount; n++ )
      int a = n % 3;
  });
  return 0;
}

boost :: timer 은 아마도 필요한만큼의 정확도를 제공 할 것입니다. 시간 a = a+1;얼마나 걸릴지 알 수있을만큼 정확 하지는 않지만 몇 나노초가 걸리는 이유는 무엇입니까?


함수 호출을 N 번 호출하고 평균을 반환하는 람다를 만들었습니다.

double c = BENCHMARK_CNT(25, fillVectorDeque(variable));

c ++ 11 헤더는 여기에서 찾을 수 있습니다 .


크로노 라이브러리의 high_resolution_clock : https://github.com/nfergu/codetimer을 사용하여 코드 블록의 성능을 측정하는 간단한 유틸리티를 만들었습니다 .

다른 키에 대해 타이밍을 기록 할 수 있으며 각 키의 타이밍에 대한 집계 된보기를 표시 할 수 있습니다.

사용법은 다음과 같습니다.

#include <chrono>
#include <iostream>
#include "codetimer.h"

int main () {
    auto start = std::chrono::high_resolution_clock::now();
    // some code here
    CodeTimer::record("mykey", start);
    CodeTimer::printStats();
    return 0;
}

[cxx-rtimers][1]로컬 변수를 생성 할 수있는 코드 블록의 런타임에 대한 통계를 수집하기위한 헤더 전용 루틴을 제공하는 GitHub를 살펴볼 수도 있습니다 . 이러한 타이머에는 C ++ 11에서 std :: chrono를 사용하거나 Boost 라이브러리의 타이머 또는 표준 POSIX 타이머 기능을 사용하는 버전이 있습니다. 이 타이머는 함수 내에서 소비 된 평균, 최대 및 최소 지속 시간과 호출 횟수를보고합니다. 다음과 같이 간단하게 사용할 수 있습니다.

#include <rtimers/cxx11.hpp>

void expensiveFunction() {
    static rtimers::cxx11::DefaultTimer timer("expensive");
    auto scopedStartStop = timer.scopedStart();
    // Do something costly...
}

참고 URL : https://stackoverflow.com/questions/1861294/how-to-calculate-execution-time-of-a-code-snippet-in-c

반응형