Как рассчитать время выполнения фрагмента кода в C ++ - PullRequest
116 голосов
/ 07 декабря 2009

Мне нужно вычислить время выполнения фрагмента кода 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 ++.

У вас есть решение?

Ответы [ 16 ]

113 голосов
/ 07 декабря 2009

Вы можете использовать эту функцию, которую я написал. Вы вызываете GetTimeMs64(), и он возвращает количество миллисекунд, прошедших с эпохи Unix с использованием системных часов - точно так же как time(NULL), за исключением миллисекунд.

Работает как на Windows, так и на Linux; это потокобезопасно.

Обратите внимание, что гранулярность на окнах составляет 15 мс; в Linux это зависит от реализации, но обычно это также 15 мс.

#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
}
42 голосов
/ 07 декабря 2009

У меня есть другой рабочий пример, который использует микросекунды (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

35 голосов
/ 20 октября 2013

Вот простое решение в 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_;
};

Или на * nix, для c ++ 03

#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

18 голосов
/ 23 августа 2011
#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

5 голосов
/ 07 декабря 2009

Windows предоставляет функцию QueryPerformanceCounter (), а Unix имеет функцию gettimeofday (). Обе функции могут измерять разницу не менее 1 микросекунды.

3 голосов
/ 26 февраля 2010

В некоторых программах, которые я написал, я использовал RDTS для этой цели. RDTSC не о времени, а о количестве циклов от запуска процессора. Вы должны откалибровать его в своей системе, чтобы получить результат за секунду, но это действительно удобно, когда вы хотите оценить производительность, еще лучше использовать количество циклов напрямую, не пытаясь изменить их обратно на секунды.

(ссылка выше на страницу французской википедии, но там есть примеры кода на C ++, английская версия здесь )

2 голосов
/ 07 декабря 2017

(решение для 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;
}
2 голосов
/ 25 сентября 2016

Полное надежное решение для планирования потоков, которое должно давать одинаковое время для каждого теста, состоит в том, чтобы скомпилировать вашу программу, чтобы она была независимой от ОС, и загрузить компьютер, чтобы запустить программу в среде без ОС. Тем не менее, это в значительной степени нецелесообразно и будет трудно в лучшем случае. Хорошей заменой освобождению от ОС является просто установление привязки текущего потока к 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;
}

Также, кредит Майку Джарвису за его Таймер.

Обратите внимание (это очень важно), что если вы собираетесь запускать фрагменты кода большего размера, вам действительно необходимо уменьшить количество итераций, чтобы предотвратить зависание компьютера.

2 голосов
/ 08 декабря 2009

Лучше запускать внутренний цикл несколько раз с синхронизацией производительности только один раз и в среднем путем деления повторений внутреннего цикла, чем запускать весь цикл (цикл + синхронизация производительности) несколько раз и в среднем. Это уменьшит накладные расходы на код синхронизации производительности по сравнению с вашим фактическим профилированным разделом.

Завершение вызовов по таймеру для соответствующей системы. Для Windows QueryPerformanceCounter довольно быстрый и «безопасный» в использовании.

Вы также можете использовать "rdtsc" на любом современном ПК с архитектурой X86, но могут быть проблемы на некоторых многоядерных машинах (скачкообразная перестройка ядра может изменить таймер) или если у вас включен какой-то скоростной шаг.

2 голосов
/ 07 декабря 2009

Я предлагаю использовать стандартные библиотечные функции для получения информации о времени из системы.

Если вы хотите более точное разрешение, выполните больше итераций выполнения. Вместо того, чтобы запускать программу один раз и получать образцы, запустите ее 1000 и более раз.

...