Таймер, чтобы найти истекшее время в вызове функции в C - PullRequest
1 голос
/ 12 марта 2009

Я хочу вычислить время, прошедшее во время вызова функции в C, с точностью до 1 наносекунды.

Есть ли в С функция таймера для этого?

Если да, предоставьте образец кода.

Псевдокод

Timer.Start()
foo();
Timer.Stop()
Display time elapsed in execution of foo()

Подробности среды: - с использованием компилятора gcc 3.4 на машине RHEL

Ответы [ 11 ]

5 голосов
/ 12 марта 2009

Могу я спросить, какой процессор вы используете? Если вы используете процессор x86, вы можете посмотреть на счетчик меток времени (tsc). Этот фрагмент кода:

#define rdtsc(low,high) \
     __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))

установит число циклов, которое ЦП выполнял в low и high соответственно (ожидается 2 long с; вы можете сохранить результат в long long int) следующим образом:

inline void getcycles (long long int * cycles)
{
  unsigned long low;
  long high;
  rdtsc(low,high);
  *cycles = high; 
  *cycles <<= 32; 
  *cycles |= low; 
}

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

Чтобы выполнить вышеизложенное, я проанализировал строку "CPU MHz" из /proc/cpuinfo и преобразовал ее в десятичную. После этого это всего лишь математика, и помните, что 1 МГц = 1 000 000 циклов в секунду и что это 1 млрд нс / с.

4 голосов
/ 13 марта 2009

Используйте clock_gettime(3). Для получения дополнительной информации введите man 3 clock_gettime. Это, как говорится, наносекундная точность редко необходима.

4 голосов
/ 12 марта 2009

На Intel и совместимых процессорах вы можете использовать инструкцию rdtsc, которую можно легко обернуть в блок asm () кода C. Возвращает значение встроенного счетчика тактов процессора, которое увеличивается с каждым циклом. Вы получаете высокое разрешение, и такое время очень быстрое.

Чтобы определить, насколько быстро это значение нужно будет откалибровать - вызовите эту инструкцию дважды за фиксированный период времени, например, пять секунд. Если вы делаете это на процессоре, который смещает частоту для снижения энергопотребления, у вас могут возникнуть проблемы с калибровкой.

2 голосов
/ 12 марта 2009

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

Стандартное решение в системах POSIX - gettimeofday(), но с точностью до микросекунды.

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

1 голос
/ 12 марта 2009

В c нет таймера, который бы гарантировал точность в 1 наносекунду. Возможно, вы захотите взглянуть на clock() или еще лучше. POSIX gettimeofday()

0 голосов
/ 23 февраля 2017

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

CLOCK_PROCESS_CPUTIME_ID resolution: 0 sec 1 nano
clock_gettime 4194304 iterations : 459.427311 msec 0.110 microsec / call
CLOCK_MONOTONIC resolution: 0 sec 1 nano
clock_gettime 4194304 iterations : 64.498347 msec 0.015 microsec / call
CLOCK_REALTIME resolution: 0 sec 1 nano
clock_gettime 4194304 iterations : 65.494828 msec 0.016 microsec / call
CLOCK_THREAD_CPUTIME_ID resolution: 0 sec 1 nano
clock_gettime 4194304 iterations : 427.133157 msec 0.102 microsec / call
rdtsc 4194304 iterations : 115.427895 msec 0.028 microsec / call
Dummy 16110479703957395943
rdtsc in milliseconds 4194304 iterations : 197.259866 msec 0.047 microsec / call
Dummy 4.84682e+08 UltraHRTimerMs 197 HRTimerMs 197.26

#include <time.h>
#include <cstdio>
#include <string>
#include <iostream>
#include <chrono>
#include <thread>

enum { TESTRUNS = 1024*1024*4 };

class HRCounter
{
private:
    timespec start, tmp;
public:
    HRCounter(bool init = true)
    {
        if(init)
            SetStart();
    }

    void SetStart()
    {
        clock_gettime(CLOCK_MONOTONIC, &start);
    }

    double GetElapsedMs()
    {
        clock_gettime(CLOCK_MONOTONIC, &tmp);
        return (double)(tmp.tv_nsec - start.tv_nsec) / 1000000 + (tmp.tv_sec - start.tv_sec) * 1000;
    }
};

__inline__ uint64_t rdtsc(void) {
    uint32_t lo, hi;
    __asm__ __volatile__ (      // serialize
    "xorl %%eax,%%eax \n        cpuid"
    ::: "%rax", "%rbx", "%rcx", "%rdx");
    /* We cannot use "=A", since this would use %rax on x86_64 and return only the lower 32bits of the TSC */
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return (uint64_t)hi << 32 | lo;
}

inline uint64_t GetCyclesPerMillisecondImpl()
{
    uint64_t start_cyles = rdtsc();
    HRCounter counter;
    std::this_thread::sleep_for (std::chrono::seconds(3));
    uint64_t end_cyles = rdtsc();
    double elapsed_ms = counter.GetElapsedMs();
    return (end_cyles - start_cyles) / elapsed_ms;
}

inline uint64_t GetCyclesPerMillisecond()
{
    static uint64_t cycles_in_millisecond = GetCyclesPerMillisecondImpl();
    return cycles_in_millisecond;
}

class UltraHRCounter
{
private:
    uint64_t start_cyles;
public:
    UltraHRCounter(bool init = true)
    {
        GetCyclesPerMillisecond();
        if(init)
            SetStart();
    }

    void SetStart() { start_cyles = rdtsc(); }

    double GetElapsedMs()
    {
        uint64_t end_cyles = rdtsc();
        return (end_cyles - start_cyles) / GetCyclesPerMillisecond();
    }
};

int main()
{
    auto Run = [](std::string const& clock_name, clockid_t clock_id)
    {
        HRCounter counter(false);
        timespec spec;
        clock_getres( clock_id, &spec );
        printf("%s resolution: %ld sec %ld nano\n", clock_name.c_str(), spec.tv_sec, spec.tv_nsec );
        counter.SetStart();
        for ( int i = 0 ; i < TESTRUNS ; ++ i )
        {
            clock_gettime( clock_id, &spec );
        }
        double fb = counter.GetElapsedMs();
        printf( "clock_gettime %d iterations : %.6f msec %.3f microsec / call\n", TESTRUNS, ( fb ), (( fb ) * 1000) / TESTRUNS );
    };

    Run("CLOCK_PROCESS_CPUTIME_ID",CLOCK_PROCESS_CPUTIME_ID);
    Run("CLOCK_MONOTONIC",CLOCK_MONOTONIC);
    Run("CLOCK_REALTIME",CLOCK_REALTIME);
    Run("CLOCK_THREAD_CPUTIME_ID",CLOCK_THREAD_CPUTIME_ID);

    {
        HRCounter counter(false);
        uint64_t dummy;
        counter.SetStart();
        for ( int i = 0 ; i < TESTRUNS ; ++ i )
        {
            dummy += rdtsc();
        }
        double fb = counter.GetElapsedMs();
        printf( "rdtsc %d iterations : %.6f msec %.3f microsec / call\n", TESTRUNS, ( fb ), (( fb ) * 1000) / TESTRUNS );
        std::cout << "Dummy " << dummy << std::endl;
    }

    {
        double dummy;
        UltraHRCounter ultra_hr_counter;
        HRCounter counter;
        for ( int i = 0 ; i < TESTRUNS ; ++ i )
        {
            dummy += ultra_hr_counter.GetElapsedMs();
        }
        double fb = counter.GetElapsedMs();
        double final = ultra_hr_counter.GetElapsedMs();
        printf( "rdtsc in milliseconds %d iterations : %.6f msec %.3f microsec / call\n", TESTRUNS, ( fb ), (( fb ) * 1000) / TESTRUNS );
        std::cout << "Dummy " << dummy << " UltraHRTimerMs " << final << " HRTimerMs " << fb << std::endl;
    }



    return 0;
}
0 голосов
/ 11 мая 2010

Вы просите что-то, что невозможно таким образом. Вам понадобится поддержка уровня HW, чтобы достичь этого уровня точности и даже очень тщательно контролировать переменные. Что произойдет, если вы получите прерывание во время выполнения вашего кода? Что если ОС решит запустить какой-то другой фрагмент кода?

А что делает твой код? Использует ли он оперативную память? Что если ваш код и / или данные находятся или не находятся в кеше?

В некоторых средах вы можете использовать счетчики уровня HW для этой работы, если вы контролируете эти переменные. Но как предотвратить переключение контекста в Linux?

Например, в инструментах DSP компании Texas Instruments (Code Composer Studio) вы можете очень точно профилировать код, поскольку вся среда отладки настроена таким образом, что эмулятор (например, Blackhawk) получает информацию о каждом запуске операции. Вы также можете установить точки наблюдения, которые кодируются непосредственно в блок HW внутри чипа в некоторых процессорах. Это работает, потому что дорожки памяти также направляются в этот блок отладки.

Они предлагают функции в своих CSL (Chip Support Library), которые вы запрашиваете, поскольку накладные расходы по времени составляют несколько циклов. Но это доступно только для их процессоров и полностью зависит от чтения значений таймера из регистров HW.

0 голосов
/ 13 марта 2009

Вы можете использовать стандартные системные вызовы, такие как gettimeofday, если вы уверены, что ваш процесс получает 100% времени процессора. Я могу вспомнить много ситуаций, в которых при выполнении foo () другие потоки и процессы могут украсть процессорное время.

0 голосов
/ 12 марта 2009

Можете ли вы просто запустить его 10 ^ 9 раз и запустить секундомер?

0 голосов
/ 12 марта 2009

Делать тесты по этой шкале не очень хорошая идея. У вас есть накладные расходы, чтобы получить как минимум время, что может сделать ваши результаты ненадежными, если вы работаете на наносекундах. Вы можете использовать системные вызовы вашей платформы или boost :: Date_Time в большем масштабе [предпочтительнее].

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...