Как рассчитать время выполнения фрагмента кода в С++

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

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

Ответ 1

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

Он работает как с окнами, так и с 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
}

Ответ 2

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

Ответ 3

Вот простое решение в С++ 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, для С++ 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

Ответ 4

#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 выходит за пределы области видимости, он распечатает время, прошедшее с момента его создания.

UPDATE. Я сделал простую отдельную замену (OSX/iOS, но легко переносится): https://github.com/catnapgames/TestTimerScoped

Ответ 5

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

Ответ 6

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

(ссылка выше на французскую страницу wikipedia, но она содержит образцы кода на С++, английская версия здесь)

Ответ 7

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

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

Ответ 8

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

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

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

Ответ 9

Полное надежное решение для планирования потоков, которое должно давать одинаковое время для каждого теста, состоит в том, чтобы скомпилировать вашу программу, чтобы она не зависела от ОС, и загрузить компьютер, чтобы запустить программу в среде без ОС. Тем не менее, это в значительной степени непрактично и в лучшем случае будет сложно.

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

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

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

Ответ 10

(специальное решение для Windows) Текущий (около 2017) способ получить точные тайминги под окнами - использовать "QueryPerformanceCounter". Этот подход имеет преимущество при предоставлении очень точных результатов и рекомендуется MS. Просто верните код blob в новое консольное приложение, чтобы получить рабочий образец. Здесь есть продолжительная дискуссия: Приобретение штампов с высоким разрешением

#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;
}

Ответ 11

В случаях, когда вы хотите, чтобы каждый раз, когда он запускался (например, для профилирования кода, который, по вашему мнению, является узким местом), вы хотите использовать один и тот же фрагмент кода), вот обертка вокруг (небольшая модификация) функции Андреаса Бонини, что я находят полезным:

#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;
};

Ответ 12

просто простой класс, который тестирует кодовый блок:

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;
}

Ответ 13

boost:: timer, вероятно, даст вам столько же точности, сколько вам нужно. Это нигде не достаточно точно, чтобы рассказать вам, сколько времени займет a = a+1;, но по какой причине вам нужно было бы время что-то, что займет пару наносекунд?

Ответ 14

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

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

Вы можете найти заголовок С++ 11 здесь.

Ответ 15

Я создал простую утилиту для измерения производительности блоков кода, используя библиотеку chrono 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;
}

Ответ 16

Вы также можете взглянуть на [cxx-rtimers][1] на GitHub, который предоставляет некоторые подпрограммы только для заголовков для сбора статистики во время выполнения любого кодового блока, где вы можете создать локальную переменную. Эти таймеры имеют версии, которые используют std :: chrono на С++ 11, или таймеры из библиотеки Boost, или стандартные функции таймера POSIX. Эти таймеры сообщат о средней, максимальной и минимальной продолжительности использования функции, а также о количестве ее вызовов. Их можно использовать просто так:

#include <rtimers/cxx11.hpp>

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