Таймер, чтобы найти прошедшее время в вызове функции в C

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

Есть ли функция таймера, доступная в C, чтобы сделать это?

Если да, укажите образец кода.

Псевдокод

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

Сведения об окружающей среде: - с использованием компилятора gcc 3.4 на машине RHEL

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

В таймере нет таймера с гарантированной точностью в 1 наносекунду. Вы можете посмотреть в clock() или еще лучше. POSIX gettimeofday()

Ответ 6

Я не знаю, найдёте ли вы таймеры, обеспечивающие разрешение одной наносекунды - это будет зависеть от разрешения системных часов, но вы можете посмотреть http://code.google.com/p/high-resolution-timer/. Они указывают, что они могут обеспечить разрешение микросекундного уровня для большинства Linux-систем и наносекунд в системах Sun.

Ответ 7

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

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

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

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

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

Ответ 11

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

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