C ++细粒度时间

下面的代码将0作为函数的运行时。 任何人都可以指出错误吗?

struct timeval start,end; long seconds,useconds; gettimeofday(&start, NULL); int optimalpfs=optimal(n,ref,count); gettimeofday(&end, NULL); seconds = end.tv_sec - start.tv_sec; useconds = end.tv_usec - start.tv_usec; long opt_runtime = ((seconds) * 1000 + useconds/1000.0) + 0.5; cout<<"\nOptimal Runtime is "<<opt_runtime<<"\n"; 

我得到的开始和结束时间都相同。我得到以下输出

 Optimal Runtime is 0 

请告诉我错误。

POSIX 1003.1b-1993指定了clock_gettime() (和clock_getres() )的接口,并提供了带MON选项的时钟,可以有一种clockid_t值为CLOCK_MONOTONIC的时钟(这样你的定时器不受系统时间的影响)调整)。 如果在您的系统上可用,那么这些函数将返回一个结构,其分辨率可能低至1纳秒,但后者的function将告诉您时钟的确切分辨率。

  struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* and nanoseconds */ }; 

您可能仍需要多次循环运行测试function,以便时钟在超过其分辨率的任何时间内进行注册,并且您可能希望运行循环足够的时间以至少比时间长至少一个数量级。时钟的分辨率。

请注意,显然Linux人员误读了POSIX.1b规范和/或不理解单调增加的时钟的定义,并且他们的CLOCK_MONOTONIC时钟受系统时间调整的影响,所以你必须使用他们发明的非CLOCK_MONOTONIC_RAW时钟获得真正的单调时钟。

或者,可以使用相关的POSIX.1 timer_settime()调用来设置定时器运行,信号处理程序捕获定时器传递的信号,以及timer_getoverrun()以查找信号排队与其之间经过的时间。最终传递,然后将循环设置为运行直到计时器关闭,计算设置的时间间隔中的迭代次数加上超限。

当然,对于抢占式多任务系统,这些时钟和计时器即使在您的进程未运行时也会运行,因此它们对于基准测试并不是非常有用。

稍微更为罕见的是clockid_t的可选POSIX.1-1999 clockid_t值,由中的_POSIX_CPUTIME表示,表示调用进程的CPU时间时钟,给出表示执行时间量的值调用过程。 (更为罕见的是clockid_tCLOCK_THREAD_CPUTIME_ID的TCT选项,由_POSIX_THREAD_CPUTIME宏指示,表示CPU时钟,给出表示调用线程执行时间的值。)

不幸的是,POSIX没有提到这些所谓的CPUTIME时钟是仅仅计算用户时间,还是由进程或线程累积的用户和系统(和中断)时间,所以如果你的配置文件中的代码进行任何系统调用,那么在内核模式中花费的时间可能会或可能不会被表示。

更糟糕的是,在多处理器系统上,如果您的进程在执行期间从一个CPU迁移到另一个CPU,则CPUTIME时钟的值可能完全是假的。 实现这些CPUTIME时钟的定时器也可能在不同的CPU内核上以不同的速度运行,并且在不同的时间,进一步使它们的含义复杂化。 即它们可能并不意味着与实际挂钟时间相关的任何事情,但仅表示CPU周期数(对于基准测试可能仍然有用,只要始终使用相对时间并且用户意识到执行时间可能取决于外部因素)。 更糟糕的是,据报道,在Linux CPU上,基于TimeStampCounter的CPU时钟CPU甚至可以报告进程hibernate的时间。

如果您的系统具有良好的工作getrusage()系统调用,那么它将有希望为您的进程在运行时单独消耗的每个实际用户和系统时间提供结构时间。 然而,由于这会让你回到微秒钟,所以你需要反复运行你的测试代码足够多次以获得更准确的时序,在循环之前调用getrusage()一次,之后再调用,并计算差异在给定的时间之间。 对于简单的算法,这可能意味着运行数百万次或更多次。 还要注意,在许多系统上,用户时间和系统时间之间的划分有些任意,如果在重复循环中单独检查,则一个或另一个甚至可能看起来倒退。 但是,如果您的算法没有进行系统调用,那么总结时间增量应该仍然是代码执行的合理时间。

BTW,在比较时间值时要小心,这样你就不会溢出或者在字段中以负值结束,无论是@Nim建议,还是像这样(来自NetBSD的 ):

  #define timersub(tvp, uvp, vvp) \ do { \ (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ if ((vvp)->tv_usec < 0) { \ (vvp)->tv_sec--; \ (vvp)->tv_usec += 1000000; \ } \ } while (0) 

(你甚至可能想更加偏执,因为tv_usec在范围内)

关于基准测试的另一个重要注意事项:确保实际调用函数,理想情况是检查编译器的汇编输出。 在驱动程序循环的单独源模块中编译函数通常会说服优化器保持调用。 另一个技巧是让它返回一个在循环内分配给定义为volatile的变量的值。

你在这里有漂浮的浮子和整体组合:

 long opt_runtime = ((seconds) * 1000 + useconds/1000.0) + 0.5; 

尝试使用:

 long opt_runtime = (long)(seconds * 1000 + (float)useconds/1000); 

这样您就可以在几毫秒内得到结果。

optimal(...)的执行时间小于gettimeofday(...)的粒度。 这可能发生在Windows上。 在Windows上,典型的粒度最长为20毫秒。 我在这里回答了一个相关的gettimeofday(…)问题。 对于Linux,我问过linux gettimeofday()的微秒时间是如何获得的,它的准确度是多少? 并取得了不错的成绩。

本 SO答案中描述了有关如何获得精确定时的更多信息。

我通常做这样的计算:

 long long ss = start.tv_sec * 1000000LL + start.tv_usec; long long es = end.tv_sec * 1000000LL + end.tv_usec; 

然后做一些改变

 long long microsec_diff = es - ss; 

现在按要求转换:

 double seconds = microsec_diff / 1000000.; 

通常情况下,我不打扰最后一步,以微秒为单位做所有时间。