Как измерить время выполнения библиотечных функций C math.h? - PullRequest
1 голос
/ 28 марта 2020

Используя заголовок time.h, я получаю время выполнения sqrt() как 2 наносекунды (с командой gcc в терминале Linux) и 44 наносекунды (с командой g++ в терминале Ubuntu ). Может кто-нибудь сказать мне какой-либо другой метод для измерения времени выполнения библиотечных функций math.h?

Ниже приведен код:

#include <time.h>
#include <stdio.h>
#include<math.h>

int main()
{

    time_t begin,end; // time_t is a datatype to store time values.
    time (&begin); // note time before execution
    for(int i=0;i<1000000000;i++)  //using for loop till 10^9 times to make the execution time in nanoseconds
    {
        cbrt(9999999);  // calling the cube root function from math library
    }
    time (&end); // note time after execution
    double difference = difftime (end,begin);
    printf ("time taken for function() %.2lf in Nanoseconds.\n", difference );
    printf(" cube root is :%f \t",cbrt(9999999));

    return 0;
}

ВЫХОД:

by using **gcc**: time taken for function() 2.00 seconds.
                  cube root is :215.443462
by using **g++**: time taken for function() 44.00 in Nanoseconds.
                  cube root is:215.443462

Linux конечный результат

Дать или взять длину приглашения:

$ g++ t1.c
$ ./a.out
time taken for function() 44.00 in Nanoseconds.
 cube root is :215.443462
$ gcc t1.c
$ ./a.out
time taken for function() 2.00 in Nanoseconds.
 cube root is :215.443462
$

Ответы [ 3 ]

1 голос
/ 28 марта 2020

как измерить время выполнения библиотечных функций c math.h?

C компиляторам часто разрешено анализировать хорошо известную стандартную библиотеку функции и замените такой код исправления, как cbrt(9999999); на 215.443462.... Кроме того, поскольку удаление функции в l oop не влияет на функцию кода, можно оптимизировать l oop.

Использование volatile предотвращает много этого, поскольку компилятор не может не оказывать никакого влияния при замене или удалении функции.

for(int i=0;i<1000000000;i++) {
    // cbrt(9999999);
    volatile double x = 9999999.0;
    volatile double y = cbrt(x);
}

Степень детализации time() часто составляет всего 1 секунду, а если миллиард циклов приводит только к нескольким секунд, рассмотрим больше циклов.


Код, который можно использовать ниже, чтобы отсеять накладные расходы l oop.

time_t begin,middle,end;
time (&begin);
for(int i=0;i<1000000000;i++) {
    volatile double x = 9999999.0;
    volatile double y = x;
}
time (&middle);
for(int i=0;i<1000000000;i++) {
    volatile double x = 9999999.0;
    volatile double y = cbrt(x);
}
time (&end);
double difference = difftime(end,middle) - difftime(middle,begin);
1 голос
/ 28 марта 2020

Синхронизирующий код - это искусство, и одна из частей искусства заключается в том, чтобы компилятор не оптимизировал ваш код. Для стандартных функций библиотеки компилятор может хорошо знать, что он делает и может вычислять константу во время компиляции. В вашем примере вызов cbrt(9999999); дает две возможности для оптимизации. Значение из cbrt() может быть оценено во время компиляции, поскольку аргумент является константой. Во-вторых, возвращаемое значение не используется, и стандартная функция не имеет побочных эффектов, поэтому компилятор может полностью его удалить. Вы можете избежать этих проблем, захватив результат (например, оценив сумму корней куба от 0 до одного миллиарда (минус один) и напечатав это значение после временного кода.

tm97.c

Когда я скомпилировал ваш код, без комментариев, я получил:

$ cat tm97.c
#include <time.h>
#include <stdio.h>
#include <math.h>

int main(void)
{
    time_t begin, end;
    time(&begin);
    for (int i = 0; i < 1000000000; i++)
    {
        cbrt(9999999);
    }
    time(&end);
    double difference = difftime(end, begin);
    printf("time taken for function() %.2lf in Nanoseconds.\n", difference );
    printf(" cube root is :%f \t", cbrt(9999999));

    return 0;
}
$ make tm97
gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes         tm97.c -o tm97 -L../lib -lsoq 
tm97.c: In function ‘main’:
tm97.c:11:9: error: statement with no effect [-Werror=unused-value]
   11 |         cbrt(9999999);
      |         ^~~~
cc1: all warnings being treated as errors
rmk: error code 1
$

Я использую G CC 9.3.0 на MacBook Pro 2017 года, работающем под MacOS Mojave 10.14.6 с XCode 11.3.1 (11C504) и G CC 9.3.0 - XCode 11.4 требует Catalina 10.15.2, но пока не удалось организовать поддержку для этого. Интересно, что когда тот же код компилируется с помощью g++, он компилируется без предупреждений (ошибок):

$ ln -s tm97.c tm89.cpp
make tm89 SXXFLAGS=-std=c++17 CXX=g++
g++ -O3 -g  -I../inc -std=c++17 -Wall -Wextra -Werror -L../lib tm89.cpp -lsoq -o tm89
$

Я обычно использую некоторый временной код, который доступен в моем репозитории SOQ (вопросы переполнения стека) на GitHub в виде файлов timer.c и timer.h в подкаталоге src / libsoq . Код скомпилирован в моей библиотеке только как код C, поэтому я создал простой заголовок оболочки timer2.h, чтобы нижеприведенные программы могли использовать #include "timer2.h" и он будет работать нормально с обоими * Компиляции 1126 * и C ++:

#ifndef TIMER2_H_INCLUDED
#define TIMER2_H_INCLUDED

#ifdef __cplusplus
extern "C" {
#endif
#include "timer.h"
#ifdef __cplusplus
}
#endif

#endif /* TIMER2_H_INCLUDED */

tm29.cpp и tm31.c

Этот код использует для тестирования функцию sqrt(). Накапливается сумма квадратных корней. Он использует временной код от timer.h / timer.c вокруг вашего временного кода - введите Clock и функции clk_init(), clk_start(), clk_stop() и clk_elapsed_us() для оценки прошедшего времени в микросекундах между тем, когда часы были запущены и в последний раз остановились.

Исходный код может быть скомпилирован либо C компилятором, либо компилятором C ++.

#include <time.h>
#include <stdio.h>
#include <math.h>
#include "timer2.h"

int main(void)
{
    time_t begin, end;
    double sum = 0.0;
    int i;
    Clock clk;
    clk_init(&clk);
    clk_start(&clk);
    time(&begin);
    for (i = 0; i < 1000000000; i++)
    {
        sum += sqrt(i);
    }
    time(&end);
    clk_stop(&clk);
    double difference = difftime(end, begin);
    char buffer[32];
    printf("Time taken for sqrt() is %.2lf nanoseconds (%s ns).\n",
           difference, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
    printf("Sum of square roots from 0 to %d is: %f\n", i, sum);

    return 0;
}

tm41.c и tm43.cpp

Этот код практически идентичен предыдущему коду, но проверенная функция - это функция cbrt() (куб root).

#include <time.h>
#include <stdio.h>
#include <math.h>
#include "timer2.h"

int main(void)
{
    time_t begin, end;
    double sum = 0.0;
    int i;
    Clock clk;
    clk_init(&clk);
    clk_start(&clk);
    time(&begin);
    for (i = 0; i < 1000000000; i++)
    {
        sum += cbrt(i);
    }
    time(&end);
    clk_stop(&clk);
    double difference = difftime(end, begin);
    char buffer[32];
    printf("Time taken for cbrt() is %.2lf nanoseconds (%s ns).\n",
           difference, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
    printf("Sum of cube roots from 0 to %d is: %f\n", i, sum);

    return 0;
}

tm59.c и tm61.c

Этот код использует fabs() вместо sqrt() или cbrt(). Это все еще вызов функции, но он может быть встроенным. Он вызывает преобразование из int в double явно; без этого приведения G CC жалуется, что вместо него следует использовать целочисленную функцию abs().

#include <time.h>
#include <stdio.h>
#include <math.h>
#include "timer2.h"

int main(void)
{
    time_t begin, end;
    double sum = 0.0;
    int i;
    Clock clk;
    clk_init(&clk);
    clk_start(&clk);
    time(&begin);
    for (i = 0; i < 1000000000; i++)
    {
        sum += fabs((double)i);
    }
    time(&end);
    clk_stop(&clk);
    double difference = difftime(end, begin);
    char buffer[32];
    printf("Time taken for fabs() is %.2lf nanoseconds (%s ns).\n",
           difference, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
    printf("Sum of absolute values from 0 to %d is: %f\n", i, sum);

    return 0;
}

tm73.cpp

Этот файл использует исходный код с моей оболочкой синхронизации код тоже. Версия C не компилируется - версия C ++ делает:

#include <time.h>
#include <stdio.h>
#include <math.h>
#include "timer2.h"

int main(void)
{
    time_t begin, end;
    Clock clk;
    clk_init(&clk);
    clk_start(&clk);
    time(&begin);
    for (int i = 0; i < 1000000000; i++)
    {
        cbrt(9999999);
    }
    time(&end);
    clk_stop(&clk);
    double difference = difftime(end, begin);
    char buffer[32];
    printf("Time taken for cbrt() is %.2lf nanoseconds (%s ns).\n",
           difference, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
    printf("Cube root is: %f\n", cbrt(9999999));

    return 0;
}

Время

Используя команду timecmd, которая сообщает время запуска и остановки, а также PID программ как Помимо временного кода, встроенного в различные команды (это вариант темы time), я получил следующие результаты. (rmk - это просто альтернативная реализация make.)

$ for prog in tm29 tm31 tm41 tm43 tm59 tm61 tm73
> do rmk $prog && timecmd -ur -- $prog
> done
g++ -O3 -g -I../inc -std=c++11 -Wall -Wextra -Werror tm29.cpp -o tm29 -L../lib -lsoq 
2020-03-28 08:47:50.040227 [PID 19076] tm29
Time taken for sqrt() is 1.00 nanoseconds (1.700296 ns).
Sum of square roots from 0 to 1000000000 is: 21081851051977.781250
2020-03-28 08:47:51.747494 [PID 19076; status 0x0000]  -  1.707267s  -  tm29
gcc -O3 -g -I../inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes tm31.c -o tm31 -L../lib -lsoq 
2020-03-28 08:47:52.056021 [PID 19088] tm31
Time taken for sqrt() is 1.00 nanoseconds (1.679867 ns).
Sum of square roots from 0 to 1000000000 is: 21081851051977.781250
2020-03-28 08:47:53.742383 [PID 19088; status 0x0000]  -  1.686362s  -  tm31
gcc -O3 -g -I../inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes tm41.c -o tm41 -L../lib -lsoq 
2020-03-28 08:47:53.908285 [PID 19099] tm41
Time taken for cbrt() is 7.00 nanoseconds (6.697999 ns).
Sum of cube roots from 0 to 1000000000 is: 749999999499.628418
2020-03-28 08:48:00.613357 [PID 19099; status 0x0000]  -  6.705072s  -  tm41
g++ -O3 -g -I../inc  -std=c++11 -Wall -Wextra -Werror tm43.cpp -o tm43 -L../lib -lsoq 
2020-03-28 08:48:00.817975 [PID 19110] tm43
Time taken for cbrt() is 7.00 nanoseconds (6.614539 ns).
Sum of cube roots from 0 to 1000000000 is: 749999999499.628418
2020-03-28 08:48:07.438298 [PID 19110; status 0x0000]  -  6.620323s  -  tm43
gcc -O3 -g -I../inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes tm59.c -o tm59 -L../lib -lsoq 
2020-03-28 08:48:07.598344 [PID 19121] tm59
Time taken for fabs() is 1.00 nanoseconds (1.114822 ns).
Sum of absolute values from 0 to 1000000000 is: 499999999067108992.000000
2020-03-28 08:48:08.718672 [PID 19121; status 0x0000]  -  1.120328s  -  tm59
g++ -O3 -g  -I../inc -std=c++11 -Wall -Wextra -Werror tm61.cpp -o tm61 -L../lib -lsoq 
2020-03-28 08:48:08.918745 [PID 19132] tm61
Time taken for fabs() is 2.00 nanoseconds (1.117780 ns).
Sum of absolute values from 0 to 1000000000 is: 499999999067108992.000000
2020-03-28 08:48:10.042134 [PID 19132; status 0x0000]  -  1.123389s  -  tm61
g++ -O3 -g  -I../inc -std=c++11 -Wall -Wextra -Werror tm73.cpp -o tm73 -L../lib -lsoq 
2020-03-28 08:48:10.236899 [PID 19143] tm73
Time taken for cbrt() is 0.00 nanoseconds (0.000004 ns).
Cube root is: 215.443462
2020-03-28 08:48:10.242322 [PID 19143; status 0x0000]  -  0.005423s  -  tm73
$

Я запускал программы много раз; Вышеуказанное время отражает то, что я получаю каждый раз Можно сделать ряд выводов:

  • sqrt() (1,7 нс) быстрее, чем cbrt() (6,7 нс).
  • fabs() (1,1 нс ) быстрее, чем sqrt() (1,7 нс).
  • Однако fabs() дает умеренное приближение ко времени, затрачиваемому с накладными расходами l oop и преобразованием из int в double.
  • Когда результат cbrt() не используется, компилятор удаляет l oop.
  • При компиляции с помощью компилятора C ++ код с вопроса полностью удаляет l oop оставляя только звонки на time() для измерения. Результат, напечатанный clk_elapsed_us(), представляет собой время, необходимое для выполнения кода в диапазоне от clk_start() до clk_stop() в секундах с разрешением в микросекундах - 0.000004 составляет 4 микросекунды. Это значение отмечено в ns, потому что когда l oop выполняется один миллиард раз, истекшее время в секундах также представляет время в наносекундах для одного l oop - в секунду миллиард наносекунд.
  • Время, указанное timecmd, соответствует времени, сообщенному программами. Затраты на запуск процесса (fork() и exec()) и ввод-вывод в процессе включены в значения времени, сообщенные timecmd.
  • Хотя это и не показано, время с clang и clang++ (вместо G CC 9.3.0) очень сопоставимы, хотя код cbrt() занимает около 7,5 нс за итерацию вместо 6,7 нс. Временные различия для остальных в основном шумовые.

Числовые суффиксы - все 2-ди git простых чисел. Они не имеют никакого другого значения, кроме как разделять разные программы.

0 голосов
/ 28 марта 2020

Как прокомментировал @Jonathan Leffler, компилятор может оптимизировать ваш код C / c ++. Если код C просто повторяет цикл от 0 до 1000 без каких-либо действий со счетчиком i (я имею в виду, без его печати или использования промежуточных значений в любой другой операции, indexes, et c), компилятор может , а не даже создать код сборки, соответствующий этому l oop. Возможные арифметические операции c будут даже предварительно вычислены. Для приведенного ниже кода:

int foo(int x) {
    return x * 5;
}

int main() {
    int x = 3;
    int y = foo(x);
    ...
    ...
}

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

mov $15, %eax
; compiler will not bother multiplying 5 by 3
; but just move the pre-computed '15' to register
ret
; and then return
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...