Вимірювання часу виконання функції в C ++


138

Я хочу дізнатися, скільки часу займає певна функція в моїй програмі C ++ для виконання в Linux . Згодом я хочу зробити порівняння швидкості. Я бачив кілька функцій часу, але закінчився цим з прискорення. Хроно:

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.
Брендон

Чи не це питання є дублікатом цього чи сценарії роблять рішення різними?
сіверянин

У мене є дві реалізації функції, і я хотів би знайти, яка працює краще.
сіверянин

Дуже важливо: переконайтеся, що ви включили оптимізацію . Неоптимізований код має інші вузькі місця, ніж звичайний оптимізований код, і не говорить вам нічого значимого. Довідка з оптимізації циклу C для остаточного призначення (при відключенні оптимізації компілятора) . І взагалі мікробенчмаркінг має безліч підводних каменів, особливо невиконання спочатку циклу розминки для частоти процесора та помилок сторінки: ідіоматичний спосіб оцінки продуктивності? . І ця відповідь
Пітер Кордес

Див. Також Як би ви оцінювали ефективність функції? для Google Benchmark, який дозволяє уникнути багатьох підводних каменів прокатки власного мікро-орієнтиру. Крім того, простий для () циклу порівняння займає однаковий час із будь-яким циклом, щоб дізнатися більше про те, як оптимізація взаємодіє із циклами орієнтирів, і що робити з цим.
Пітер Кордес

Відповіді:


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Дасть вам фізичне і в режимі реального часу , що ваша функція буде виконуватися. Отже, під час першого запуску ваш процесор використовувався менше, ніж у наступному. Під "використаним" я маю на увазі, яку роботу з додатками використовує процесор.
Віктор

1
Так, якщо вам потрібна середня кількість часу, це хороший спосіб її отримати. зробіть три пробіги і обчисліть середню.
Віктор

3
Не могли б ви розмістити поштовий індекс без використання простору імен взагалі. Це полегшує зрозуміти, що звідки походить.
Сніговик

1
Чи не повинно це бути steady_clock? Хіба це не можливо, це high_resolution_clockможе бути одноманітний годинник?
Gillespie

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можна використовувати
Jahid

2
Два макроси та глобальний typedef - жоден з яких не захищає жодного натискання клавіш - це, звичайно, нічого, що я б назвав елегантним. Також передача функціонального об'єкта та ідеальне пересилання аргументів окремо є трохи надмірним (а у випадку перевантажених функцій навіть незручно), коли ви можете просто вимагати введення тимчасового коду в лямбда. Але добре, якщо передавати аргументи необов’язково.
MikeMB

2
І це є виправданням для порушення кожної інструкції щодо іменування макросів? Ви не префіксуєте їх, не використовуєте великих літер, ви вибираєте дуже поширене ім’я, яке має високу ймовірність зіткнення з якимсь місцевим символом і найбільше: навіщо ви взагалі використовуєте макрос (замість функції )? І поки ми в цьому: Нащо ви повертаєте тривалість як подвійне зображення, насамперед, наносекунд? Ми, мабуть, повинні погодитися, що ми не згодні. Моя початкова думка стоїть: "Це не те, що я б назвав елегантним кодом".
MikeMB

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

2
@Jahid: Дякую У такому випадку вважайте мої коментарі недійсними та недійсними.
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
він дуже неточний, показує лише секунди, але немає мілісекунд
користувач25

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 :: дайв 2015 - Написання швидкого коду I:

Виміряний час: tm = t + tq + tn + to

де:

tm - вимірюваний (спостережуваний) час

t - фактичний час, що цікавить

tq - час, доданий шумом квантування

tn - час, доданий різними джерелами шуму

до - накладний час (функції вимірювання, циклу, виклику)

Відповідно до того, що він сказав пізніше на лекції, ви повинні взяти як мінімум цю велику кількість страт як результат. Я закликаю вас подивитися на лекцію, в якій він пояснює, чому.

Також є дуже хороша бібліотека від Google - https://github.com/google/benchmark . Ця бібліотека дуже проста у використанні та потужна. Ви можете прочитати кілька лекцій Чендлера Каррута на youtube, де він використовує цю бібліотеку на практиці. Наприклад, CppCon 2017: Carruth Chandler "Швидше нікуди";

Приклад використання:

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

EDIT: Звичайно, вам завжди потрібно пам’ятати, що ваш компілятор може щось оптимізувати чи ні. Такі засоби, як перф, можуть бути корисними в таких випадках.


Цікаво - яка користь від використання лямбда тут над шаблоном функції?
user48956

1
Основна відмінність полягала б у тому, що це об'єкт, що викликається, але дійсно, ви можете отримати щось дуже схоже з варіативним шаблоном і std :: result_of_t.
Кшиштоф

@KrzysztofSommerfeld Як це зробити для функціональних методів, коли я передаю час (Object.Method1), він повертає помилку "нестандартний синтаксис; використовую" & "для створення покажчика на члена"
RobinAtTech

timeFuncInvocation ([& objectName] (auto && ... args) {objectName.methodName (std :: вперед <decltype (args)> (args) ...);}, arg1, arg2, ...); або відпустіть і підпишіться перед objectName (тоді у вас буде копія об’єкта)
Krzysztof Sommerfeld

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

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

b) Додайте зручний макрос, який використовує поточну назву функції як TAG (тут важливо використовувати макрос, інше __FUNCTION__оцінюватиме MeasureExecutionTimeзамість функції, яку ви хочете вимірювати

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

в) Напишіть свій макрос на початку функції, яку потрібно виміряти. Приклад:

 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.