【发布时间】:2009-11-06 14:58:37
【问题描述】:
是否有一些好方法可以了解函数在 C 中的执行情况?例如,我想将我自己的函数与库函数进行比较。
【问题讨论】:
标签: c performance function
是否有一些好方法可以了解函数在 C 中的执行情况?例如,我想将我自己的函数与库函数进行比较。
【问题讨论】:
标签: c performance function
您需要高分辨率的计时器。
在 Linux 上,@987654321@ 是一个不错的选择,它为您提供微秒级分辨率。在 Windows 上,@987654322@ 是典型的。确保多次运行函数以获得稳定的读数。
快速示例,适用于 Linux:
struct timeval t0, t1;
unsigned int i;
gettimeofday(&t0, NULL);
for(i = 0; i < 100000; i++)
function_to_measure();
gettimeofday(&t1, NULL);
printf("Did %u calls in %.2g seconds\n", i, t1.tv_sec - t0.tv_sec + 1E-6 * (t1.tv_usec - t0.tv_usec));
您当然会调整计数 (100,000) 以匹配函数的性能。最好是函数确实需要一段时间才能运行,否则循环和/或函数调用开销可能会占主导地位。
【讨论】:
开源的Callgrind 分析器(适用于 Linux)是一种非常棒的性能测量方法。与 KCacheGrind 结合使用,您可以非常直观地了解您的时间花费在哪里。
Callgrind 是 Valgrind 的一部分。
【讨论】:
你好,我给你举个例子解释一下:
#include <stdio.h>
#include <time.h>
int main(void)
{
clock_t start_clk = clock();
/*
put any code here
*/
printf("Processor time used by program: %lg sec.\n", \
(clock() - start_clk) / (long double) CLOCKS_PER_SEC);
return 0;
}
输出:程序使用的处理器时间:4.94066e-324 秒。
时间.h:
声明 clock_t 是一个算术(你可以像我在示例中那样对这个值进行数学运算)时间值。 基本上把任何代码放在注释所在的地方。
CLOCKS_PER_SEC是time.h中声明的宏,用它作为分母将值转换为秒。
必须将其强制转换为 long double 原因有两个:
【讨论】:
运行它(他们)数百万次(每次)并测量它所花费的时间。
完成得越快,性能越好。
gprof 可以帮忙:)
这是我运行我的程序 10 秒时 gprof 的结果(函数名称已更改)
每个样本计为 0.01 秒。 % 累计自我自我总计 时间 秒 秒 呼叫 ms/呼叫 ms/呼叫名称 60.29 8.68 8.68 115471546 0.00 0.00 工作量 39.22 14.32 5.64 46 122.70 311.32 工作_b 0.49 14.39 0.07 内联 0.07 14.40 0.01 46 0.22 0.22 工作_c 0.00 14.40 0.00 460 0.00 0.00 find_minimum 0.00 14.40 0.00 460 0.00 0.00 反馈 0.00 14.40 0.00 46 0.00 0.00 work_a【讨论】:
Fred,我注意到您在评论中说您使用的是 OS X。在 OS X 上获得非常准确的小规模函数计时的最佳方法是使用 mach_absoute_time( ) 函数。您可以按如下方式使用它:
#include <mach/mach_time.h>
#include <stdint.h>
int loopCount;
uint64_t startTime = mach_absolute_time( );
for (loopCount = 0; loopCount < iterations; ++loopCount) {
functionBeingTimed( );
}
uint64_t endTime = mach_absolute_time( );
double averageTime = (double)(endTime-startTime) / iterations;
这为您提供了对函数的 iterations 调用的平均时间。这可能会受到系统进程之外的影响的影响。因此,您可能希望花费最快的时间:
#include <mach/mach_time.h>
#include <stdint.h>
int loopCount;
double bestTime = __builtin_inf();
for (loopCount = 0; loopCount < iterations; ++loopCount) {
uint64_t startTime = mach_absolute_time( );
functionBeingTimed( );
uint64_t endTime = mach_absolute_time( );
double bestTime = __builtin_fmin(bestTime, (double)(endTime-startTime));
}
这可能有其自身的问题,特别是如果正在计时的功能非常非常快。您需要考虑您真正想要测量的内容并选择一种科学合理的方法(良好的实验设计困难)。我经常使用这两种方法之间的混合作为衡量新任务(多次调用的最低平均值)的第一次尝试。
另请注意,在上面的代码示例中,时间以“马赫时间单位”为单位。如果您只想比较算法,这通常很好。出于某些其他目的,您可能希望将它们转换为纳秒或周期。为此,您可以使用以下函数:
#include <mach/mach_time.h>
#include <sys/sysctl.h>
#include <stdint.h>
double ticksToNanoseconds(double ticks) {
static double nanosecondsPerTick = 0.0;
// The first time the function is called
// ask the system how to convert mach
// time units to nanoseconds
if (0.0 == nanosecondsPerTick) {
mach_timebase_info_data_t timebase;
// to be completely pedantic, check the return code of this call:
mach_timebase_info(&timebase);
nanosecondsPerTick = (double)timebase.numer / timebase.denom;
}
return ticks * nanosecondsPerTick;
}
double nanosecondsToCycles(double nanoseconds) {
static double cyclesPerNanosecond = 0.0;
// The first time the function is called
// ask the system what the CPU frequency is
if (0.0 == cyclesPerNanosecond) {
uint64_t freq;
size_t freqSize = sizeof(freq);
// Again, check the return code for correctness =)
sysctlbyname("hw.cpufrequency", &freq, &freqSize, NULL, 0L );
cyclesPerNanosecond = (double)freq * 1e-9;
}
return nanoseconds * cyclesPerNanosecond;
}
请注意,转换为纳秒始终是正确的,但转换为周期可能会以各种方式出错,因为现代 CPU 不会以固定的速度运行。尽管如此,它通常工作得很好。
【讨论】:
所有这些其他答案都使用gettimeofday() 的某些变体进行计时。这是相当粗糙的,因为您通常需要多次运行内核才能获得可重现的结果。将其置于紧密循环中会更改代码和数据缓存的状态,因此这些结果可能无法指示实际性能。
更好的选择是实际使用 CPU 周期计数器。在 x86 上,您可以使用 rdtsc 指令执行此操作。这是来自x264:
static inline uint32_t read_time(void)
{
uint32_t a = 0;
#if defined(__GNUC__) && (defined(ARCH_X86) || defined(ARCH_X86_64))
asm volatile( "rdtsc" :"=a"(a) ::"edx" );
#elif defined(ARCH_PPC)
asm volatile( "mftb %0" : "=r" (a) );
#elif defined(ARCH_ARM) // ARMv7 only
asm volatile( "mrc p15, 0, %0, c9, c13, 0" : "=r"(a) );
#endif
return a;
}
有关使用各种硬件计数器进行分析的更多信息,请参阅PAPI。出于某些目的,模拟器(如 Callgrind 和基于中断的分析器 (Oprofile) 很有用。
【讨论】:
在进入函数之前存储系统时间。 从函数返回后存储系统时间。 减去差异并比较两个实现。
【讨论】:
查看HighResTimer 以获得高性能计时器。
您可能会发现存储之前/之后的时间不够准确,并且可能会导致 0,除非您有更长的运行时间。
【讨论】:
查看RDTSC,但最好如下所示。
0 - 调用系统的 Sleep 或 Yield 函数,以便当它返回时,您有一个新的时间片
1 - RDTSC
2 - 调用你的函数
3 - RDTSC
如果你的函数是一个长时间运行的函数,你必须使用某种分析工具,比如 gprof(它非常容易使用)和 Intel 的 VTune 应用程序(我很久没用过)。看到 Art 的回答后,我改变了主意,从 gprof 转到 Callgrind。我以前只用过 Valgrind 的 Memcheck 工具,它是一个很棒的工具。我之前没用过Callgrind,但我相信它比gprof 好...
【讨论】:
存储时间戳进入函数
存储时间戳退出函数
比较时间戳
确保使用重要的样本,因为时间分辨率可能会改变您的结果。对于短期功能尤其如此。使用高分辨率计时器(大多数平台都提供微秒分辨率)。
【讨论】:
作为最简单和可移植的方法,您可以使用标准函数 time(),它返回自 Epoch 以来的当前秒数。
#include <time.h>
time_t starttime, endtime;
starttime = time(NULL);
for (i = 0; i < 1000000; i++)
{
testfunc();
}
endtime = time(NULL);
printf("Time in seconds is %d\n", (int)(endtime-starttime));
根据您的需要调整迭代次数。如果一个函数调用需要 5 秒,那么你需要一杯咖啡进行 1000000 次迭代......当差异小于 1 秒时,即使对于一个很大的数字,你应该 1)问自己是否重要,如果是,2 ) 检查您最喜欢的编译器是否已经具有内置分析功能。
【讨论】: