Измерение времени выполнения функции в C ++


138

Я хочу узнать, сколько времени требуется в моей программе на C ++ для выполнения определенной функции в Linux . После этого я хочу провести сравнение скорости. Я видел несколько функций времени, но закончил с этим из-за повышения. Chrono:

process_user_cpu_clock, captures user-CPU time spent by the current process

Теперь мне не ясно, использую ли я вышеуказанную функцию, получу ли я единственное время, которое процессор потратил на эту функцию?

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

PS: Сейчас я использую, std::chrono::system_clock::now()чтобы получить время в секундах, но это дает мне разные результаты из-за разной загрузки процессора каждый раз.


2
Для Linux: clock_gettime.. gcc определяет другие часы как: typedef system_clock steady_clock; typedef system_clock high_resolution_clock;в Windows используйте QueryPerformanceCounter.
Brandon

Разве этот вопрос не дублирует этот или сценарии меняют решения?
северянин

У меня есть две реализации функции, и я хотел бы найти, какая из них работает лучше.
северянин

Очень важно: убедитесь, что вы включили оптимизацию . Неоптимизированный код имеет другие узкие места, чем нормальный оптимизированный код, и не говорит вам ничего значимого. Помощь по оптимизации цикла C для окончательного назначения (с отключенной оптимизацией компилятора) . И вообще, микробенчмаркинг имеет много подводных камней, особенно если сначала не выполнить цикл прогрева для ошибок частоты процессора и страниц: Идиоматический способ оценки производительности? , А этот ответ
Питер Кордес

См. Также Как бы вы оценили производительность функции? для Google Benchmark, который позволяет избежать многих ошибок, связанных с созданием собственного микробенчмарка. Также простой тест цикла for () занимает то же время, что и любой привязанный цикл, чтобы узнать больше о том, как оптимизация взаимодействует с циклами тестирования и что с этим делать.
Питер Кордес

Ответы:


264

Это очень простой в использовании метод в C ++ 11. Вы должны использовать std::chrono::high_resolution_clockиз <chrono>заголовка.

Используйте это так:

#include <iostream>
#include <chrono>

void function()
{
    long long number = 0;

    for( long long i = 0; i != 2000000; ++i )
    {
       number += 5;
    }
}

int main()
{
    auto t1 = std::chrono::high_resolution_clock::now();
    function();
    auto t2 = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();

    std::cout << duration;
    return 0;
}

Это позволит измерить продолжительность функции.

ПРИМЕЧАНИЕ. Вы не всегда получите одинаковое время для функции. Это связано с тем, что ЦП вашего компьютера может меньше или больше использоваться другими процессами, запущенными на вашем компьютере, точно так же, как ваш ум может быть более или менее сконцентрирован, когда вы решаете математическое упражнение. В человеческом уме мы можем вспомнить решение математической задачи, но для компьютера один и тот же процесс всегда будет чем-то новым; таким образом, как я уже сказал, вы не всегда будете получать одинаковый результат!


Когда я использую эту функцию, при первом запуске она дала мне 118440535 микросекунд, а при втором запуске той же функции - 83221031 микросекунды. Разве два измерения времени не должны быть равными, когда я измеряю продолжительность только этой функции?
Xara

1
Нет. Процессор вашего компьютера может использоваться меньше или больше. Это high_resolution_clockдаст вам физическое и реальное время, необходимое для выполнения вашей функции. Итак, при первом запуске ваш процессор использовался меньше, чем при следующем запуске. Под «использованным» я имею в виду, какие другие приложения используют ЦП.
Victor

1
Да, если вам нужно среднее время, это хороший способ получить его. сделайте три прогона и вычислите среднее значение.
Victor

3
Не могли бы вы опубликовать код без «использования пространства имен» в целом. Так легче увидеть, что и откуда.
Снеговик

1
Разве это не должно быть steady_clock? Разве не high_resolution_clockмогут быть немонотонные часы?
Гиллеспи

15

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

#include <chrono>
#include <utility>

typedef std::chrono::high_resolution_clock::time_point TimeVar;

#define duration(a) std::chrono::duration_cast<std::chrono::nanoseconds>(a).count()
#define timeNow() std::chrono::high_resolution_clock::now()

template<typename F, typename... Args>
double funcTime(F func, Args&&... args){
    TimeVar t1=timeNow();
    func(std::forward<Args>(args)...);
    return duration(timeNow()-t1);
}

Пример использования:

#include <iostream>
#include <algorithm>

typedef std::string String;

//first test function doing something
int countCharInString(String s, char delim){
    int count=0;
    String::size_type pos = s.find_first_of(delim);
    while ((pos = s.find_first_of(delim, pos)) != String::npos){
        count++;pos++;
    }
    return count;
}

//second test function doing the same thing in different way
int countWithAlgorithm(String s, char delim){
    return std::count(s.begin(),s.end(),delim);
}


int main(){
    std::cout<<"norm: "<<funcTime(countCharInString,"precision=10",'=')<<"\n";
    std::cout<<"algo: "<<funcTime(countWithAlgorithm,"precision=10",'=');
    return 0;
}

Вывод:

norm: 15555
algo: 2976

2
@ RestlessC0bra: это определенная реализация, high_resolution_clockможет быть псевдонимом system_clock(настенные часы) steady_clockили третьими независимыми часами. Подробности смотрите здесь . Для часов процессора std::clockможно использовать
Джахид

2
Два макроса и глобальный typedef - ни один из которых не безопасен для одного нажатия клавиши - это, конечно, не то, что я бы назвал элегантным. Кроме того, передача объекта функции и идеальная пересылка аргументов по отдельности - это немного излишне (а в случае перегруженных функций даже неудобно), когда вы можете просто потребовать, чтобы синхронизированный код был помещен в лямбду. Но хорошо, если передача аргументов необязательна.
MikeMB

2
И это оправдание для нарушения всех правил, касающихся наименования макросов? Вы не добавляете к ним префиксы, вы не используете заглавные буквы, вы выбираете очень распространенное имя, которое имеет высокую вероятность столкновения с каким-либо локальным символом, и, самое главное: почему вы вообще используете макрос (вместо функции )? И пока мы занимаемся этим: почему вы в первую очередь возвращаете длительность в виде двойного числа, представляющего наносекунды? Вероятно, мы должны согласиться с тем, что мы не согласны. Мое первоначальное мнение остается неизменным: «Это не то, что я бы назвал элегантным кодом».
MikeMB

1
Проблема в том, что они не заданы. Что меня беспокоит, так это то, что такие макросы попадают в заголовочный файл, который (возможно, косвенно как часть библиотеки) включается в мой код. Если вы хотите попробовать, что произойдет, если общие имена используются для макросов, включаемых windows.hв нетривиальный проект c ++. Касательно assertпрежде всего: "quod licet iovi non licet bovi";). Во-вторых, не все решения в стандартной библиотеке (иногда датируемые десятилетиями) на самом деле считаются хорошей идеей по современным стандартам. Есть причина, по которой разработчики модулей C ++ очень стараются не экспортировать макросы по умолчанию.
MikeMB

2
@ Джахид: Спасибо. В таком случае считайте мои комментарии недействительными.
MikeMB

9

простая программа для определения времени выполнения функции.

#include <iostream>
#include <ctime> // time_t
#include <cstdio>

void function()
{
     for(long int i=0;i<1000000000;i++)
     {
        // do nothing
     }
}

int main()
{

time_t begin,end; // time_t is a datatype to store time values.

time (&begin); // note time before execution
function();
time (&end); // note time after execution

double difference = difftime (end,begin);
printf ("time taken for function() %.2lf seconds.\n", difference );

return 0;
}

6
это очень неточно, показывает только секунды, но не миллисекунды
user25

7

В книге Скотта Мейерса я нашел пример универсального обобщенного лямбда-выражения, которое можно использовать для измерения времени выполнения функции. (C ++ 14)

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = std::chrono::high_resolution_clock::now();
        // function invocation using perfect forwarding
        std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        // get time after function invocation
        const auto& stop = std::chrono::high_resolution_clock::now();
        return stop - start;
     };

Проблема в том, что вы измеряете только одно выполнение, поэтому результаты могут сильно отличаться. Чтобы получить достоверный результат, следует замерить большое количество исполнений. Согласно лекции Андрея Александреску на конференции code :: dive 2015 - Writing Fast Code I:

Измеренное время: tm = t + tq + tn + to

где:

tm - измеренное (наблюдаемое) время

t - актуальное интересующее время

tq - время, добавленное шумом квантования

tn - время, добавленное различными источниками шума

to - время накладных расходов (измерение, цикл, вызов функций)

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

Также есть очень хорошая библиотека от Google - https://github.com/google/benchmark . Эта библиотека очень проста в использовании и мощна. Вы можете просмотреть некоторые лекции Чендлера Каррута на YouTube, где он использует эту библиотеку на практике. Например, CppCon 2017: Чендлер Каррут «Быстрее никуда не денешься»;

Пример использования:

#include <iostream>
#include <chrono>
#include <vector>
auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = high_resolution_clock::now();
        // function invocation using perfect forwarding
        for(auto i = 0; i < 100000/*largeNumber*/; ++i) {
            std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        }
        // get time after function invocation
        const auto& stop = high_resolution_clock::now();
        return (stop - start)/100000/*largeNumber*/;
     };

void f(std::vector<int>& vec) {
    vec.push_back(1);
}

void f2(std::vector<int>& vec) {
    vec.emplace_back(1);
}
int main()
{
    std::vector<int> vec;
    std::vector<int> vec2;
    std::cout << timeFuncInvocation(f, vec).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec2).count() << std::endl;
    std::vector<int> vec3;
    vec3.reserve(100000);
    std::vector<int> vec4;
    vec4.reserve(100000);
    std::cout << timeFuncInvocation(f, vec3).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec4).count() << std::endl;
    return 0;
}

РЕДАКТИРОВАТЬ: Конечно, вам всегда нужно помнить, что ваш компилятор может что-то оптимизировать или нет. В таких случаях могут быть полезны такие инструменты, как perf.


Интересно - в чем преимущество использования лямбда здесь по сравнению с шаблоном функции?
user48956

1
Основное отличие состоит в том, что это вызываемый объект, но на самом деле вы можете получить что-то очень похожее с помощью вариативного шаблона и std :: result_of_t.
Кшиштоф Зоммерфельд

@KrzysztofSommerfeld Как сделать это для методов функций, когда я передаю время (Object.Method1), он возвращает ошибку «нестандартный синтаксис; используйте '&' для создания указателя на член»
RobinAtTech

timeFuncInvocation ([& objectName] (auto && ... args) {objectName.methodName (std :: forward <decltype (args)> (args) ...);}, arg1, arg2, ...); или опустите и подпишите перед objectName (тогда у вас будет копия объекта)
Кшиштоф Зоммерфельд

4

Простой способ для более старого C ++ или C:

#include <time.h> // includes clock_t and CLOCKS_PER_SEC

int main() {

    clock_t start, end;

    start = clock();
    // ...code to measure...
    end = clock();

    double duration_sec = double(end-start)/CLOCKS_PER_SEC;
    return 0;
}

Точность отсчета времени в секундах составляет 1.0/CLOCKS_PER_SEC


1
Это не переносится. Он измеряет время процессора в Linux и время в Windows.
BugSquasher

время начала и окончания всегда одинаково, несмотря на то, что я добавляю массив из 512 элементов ... в Win64 / Visual Studio 17
Maverick

2
  • Это очень простой в использовании метод в C ++ 11.
  • Мы можем использовать std :: chrono :: high_resolution_clock из заголовка
  • Мы можем написать метод для вывода времени выполнения метода в удобочитаемой форме.

Например, чтобы найти все простые числа от 1 до 100 миллионов, требуется примерно 1 минута 40 секунд. Таким образом, время выполнения печатается как:

Execution Time: 1 Minutes, 40 Seconds, 715 MicroSeconds, 715000 NanoSeconds

Код здесь:

#include <iostream>
#include <chrono>

using namespace std;
using namespace std::chrono;

typedef high_resolution_clock Clock;
typedef Clock::time_point ClockTime;

void findPrime(long n, string file);
void printExecutionTime(ClockTime start_time, ClockTime end_time);

int main()
{
    long n = long(1E+8);  // N = 100 million

    ClockTime start_time = Clock::now();

    // Write all the prime numbers from 1 to N to the file "prime.txt"
    findPrime(n, "C:\\prime.txt"); 

    ClockTime end_time = Clock::now();

    printExecutionTime(start_time, end_time);
}

void printExecutionTime(ClockTime start_time, ClockTime end_time)
{
    auto execution_time_ns = duration_cast<nanoseconds>(end_time - start_time).count();
    auto execution_time_ms = duration_cast<microseconds>(end_time - start_time).count();
    auto execution_time_sec = duration_cast<seconds>(end_time - start_time).count();
    auto execution_time_min = duration_cast<minutes>(end_time - start_time).count();
    auto execution_time_hour = duration_cast<hours>(end_time - start_time).count();

    cout << "\nExecution Time: ";
    if(execution_time_hour > 0)
    cout << "" << execution_time_hour << " Hours, ";
    if(execution_time_min > 0)
    cout << "" << execution_time_min % 60 << " Minutes, ";
    if(execution_time_sec > 0)
    cout << "" << execution_time_sec % 60 << " Seconds, ";
    if(execution_time_ms > 0)
    cout << "" << execution_time_ms % long(1E+3) << " MicroSeconds, ";
    if(execution_time_ns > 0)
    cout << "" << execution_time_ns % long(1E+6) << " NanoSeconds, ";
}

0

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

#ifndef EXECUTION_TIMER_H
#define EXECUTION_TIMER_H

template<class Resolution = std::chrono::milliseconds>
class ExecutionTimer {
public:
    using Clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
                                     std::chrono::high_resolution_clock,
                                     std::chrono::steady_clock>;
private:
    const Clock::time_point mStart = Clock::now();

public:
    ExecutionTimer() = default;
    ~ExecutionTimer() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Destructor Elapsed: "
                  << std::chrono::duration_cast<Resolution>( end - mStart ).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }    

    inline void stop() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Stop Elapsed: "
                  << std::chrono::duration_cast<Resolution>(end - mStart).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }

}; // ExecutionTimer

#endif // EXECUTION_TIMER_H

Вот некоторые его применения:

int main() {
    { // empty scope to display ExecutionTimer's destructor's message
         // displayed in milliseconds
         ExecutionTimer<std::chrono::milliseconds> timer;

         // function or code block here

         timer.stop();

    } 

    { // same as above
        ExecutionTimer<std::chrono::microseconds> timer;

        // code block here...

        timer.stop();
    }

    {  // same as above
       ExecutionTimer<std::chrono::nanoseconds> timer;

       // code block here...

       timer.stop();

    }

    {  // same as above
       ExecutionTimer<std::chrono::seconds> timer;

       // code block here...

       timer.stop();

    }              

    return 0;
}

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


Лично stop()функция-член не нужна, потому что деструктор останавливает таймер за вас.
Кейси

@Casey Конструкция класса не обязательно требует функции остановки, однако она существует по определенной причине. Конструкция по умолчанию при создании объекта перед test codeзапуском таймера. Затем после вашего test codeвы явно используете объект таймера и вызываете его метод остановки. Вы должны вызывать его вручную, когда хотите stopиспользовать таймер. Класс не принимает никаких параметров. Кроме того, если вы использовали этот класс так же, как я показал, вы увидите, что между вызовом obj.stopи его вызовом проходит минимальное время destructor.
Фрэнсис Куглер

@Casey ... Это также позволяет иметь несколько объектов таймера в одной и той же области, не то, чтобы это действительно нужно, а просто еще один жизнеспособный вариант.
Фрэнсис Куглер,

Этот пример не может быть составлен в представленной форме. Ошибка связана с «нет соответствия для оператора << ...»!
Celdor

@Celdor нужно ли вам включить соответствующие файлы; например <chrono>?
Фрэнсис Куглер

0

Я рекомендую использовать, steady_clockкоторый гарантированно будет монотонным, в отличие от high_resolution_clock.

#include <iostream>
#include <chrono>

using namespace std;

unsigned int stopwatch()
{
    static auto start_time = chrono::steady_clock::now();

    auto end_time = chrono::steady_clock::now();
    auto delta    = chrono::duration_cast<chrono::microseconds>(end_time - start_time);

    start_time = end_time;

    return delta.count();
}

int main() {
  stopwatch(); //Start stopwatch
  std::cout << "Hello World!\n";
  cout << stopwatch() << endl; //Time to execute last line
  for (int i=0; i<1000000; i++)
      string s = "ASDFAD";
  cout << stopwatch() << endl; //Time to execute for loop
}

Вывод:

Hello World!
62
163514

0

У вас может быть простой класс, который можно использовать для такого рода измерений.

class duration_printer {
public:
    duration_printer() : __start(std::chrono::high_resolution_clock::now()) {}
    ~duration_printer() {
        using namespace std::chrono;
        high_resolution_clock::time_point end = high_resolution_clock::now();
        duration<double> dur = duration_cast<duration<double>>(end - __start);
        std::cout << dur.count() << " seconds" << std::endl;
    }
private:
    std::chrono::high_resolution_clock::time_point __start;
};

Единственное, что нужно сделать, это создать объект в вашей функции в начале этой функции.

void veryLongExecutingFunction() {
    duration_calculator dc;
    for(int i = 0; i < 100000; ++i) std::cout << "Hello world" << std::endl;
}

int main() {
    veryLongExecutingFunction();
    return 0;
}

и это все. Класс можно изменить в соответствии с вашими требованиями.


0

Поскольку ни один из предоставленных ответов не является очень точным и не дает воспроизводимых результатов, я решил добавить ссылку на свой код, который имеет субнаносекундную точность и научную статистику.

Обратите внимание, что это будет работать только для измерения кода, выполнение которого занимает (очень) короткое время (то есть от нескольких тактовых циклов до нескольких тысяч): если они работают так долго, что они могут быть прерваны каким-либо -heh- прерыванием , то явно невозможно дать воспроизводимый и точный результат; Следствием этого является то, что измерение никогда не заканчивается: а именно, оно продолжается до тех пор, пока не будет статистически на 99,9% уверено, что он имеет правильный ответ, чего никогда не происходит на машине, на которой выполняются другие процессы, когда код занимает слишком много времени.

https://github.com/CarloWood/cwds/blob/master/benchmark.h#L40


0

Если вы хотите сэкономить время и строки кода, вы можете сделать измерение времени выполнения функции однострочным макросом:

а) Реализуйте класс измерения времени, как уже предлагалось выше (вот моя реализация для Android):

class MeasureExecutionTime{
private:
    const std::chrono::steady_clock::time_point begin;
    const std::string caller;
public:
    MeasureExecutionTime(const std::string& caller):caller(caller),begin(std::chrono::steady_clock::now()){}
    ~MeasureExecutionTime(){
        const auto duration=std::chrono::steady_clock::now()-begin;
        LOGD("ExecutionTime")<<"For "<<caller<<" is "<<std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()<<"ms";
    }
};

б) Добавьте удобный макрос, который использует текущее имя функции как ТЕГ (использование макроса здесь важно, иначе __FUNCTION__будет вычисляться MeasureExecutionTimeвместо функции, которую вы хотите измерить

#ifndef MEASURE_FUNCTION_EXECUTION_TIME
#define MEASURE_FUNCTION_EXECUTION_TIME const MeasureExecutionTime measureExecutionTime(__FUNCTION__);
#endif

c) Напишите свой макрос в начале функции, которую вы хотите измерить. Пример:

 void DecodeMJPEGtoANativeWindowBuffer(uvc_frame_t* frame_mjpeg,const ANativeWindow_Buffer& nativeWindowBuffer){
        MEASURE_FUNCTION_EXECUTION_TIME
        // Do some time-critical stuff 
}

Это приведет к следующему выводу:

ExecutionTime: For DecodeMJPEGtoANativeWindowBuffer is 54ms

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.