【问题标题】:C++ fine granular timeC++细粒度时间
【发布时间】:2023-04-11 07:21:01
【问题描述】:

以下代码将 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

请告诉我错误。

【问题讨论】:

  • 检查secondsuseconds的值。
  • 只有0和0
  • 如果可能,请使用新的 C++11 std::chrono 命名空间函数和类。请参阅参考和示例here。取两个时间点的差(只是正常的减法)并获得任何分辨率的输出会更容易。
  • optimal 函数有什么作用?如果微秒差为零,则意味着该函数几乎没有做任何事情,甚至可能被编译器优化。
  • 你可以使用clock_gettime调用来获取更多的精确值(纳秒)

标签: c++ c timer


【解决方案1】:

这里有奇怪的浮点数和整数组合:

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

尝试使用:

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

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

【讨论】:

  • "seconds = end.tv_sec - start.tv_sec; useconds = end.tv_usec - start.tv_usec; "这两个值本身都是 0
  • end.tv_secend.tv_used 中包含什么值(start. 相同?
  • 结束值与开始值相同。
  • @user1783628 真的花了多长时间?
  • end.tv_sec = start.tv_sec = 1353403602, end.tv_usec = start.tv_usec = 422375。
【解决方案2】:

optimal(...)的执行时间小于gettimeofday(...)的粒度。这可能发生在 Windows 上。在 Windows 上,典型的粒度高达 20 毫秒。我已经回答了一个相关的 gettimeofday(...) 问题here。 对于 Linux,我问了How is the microsecond time of linux gettimeofday() obtained and what is its accuracy?,得到了很好的结果。

this SO 回答中描述了有关如何获得准确时间的更多信息。

【讨论】:

    【解决方案3】:

    我通常会这样计算:

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

    通常,我不关心最后一步,以微秒为单位进行所有计时。

    【讨论】:

      【解决方案4】:

      POSIX 1003.1b-1993 为clock_gettime()(和clock_getres())指定接口,并通过MON 选项提供了一种clockid_t 值为CLOCK_MONOTONIC 的时钟类型(这样您的计时器不受系统时间调整的影响)。如果在您的系统上可用,那么这些函数会返回一个潜在分辨率低至 1 纳秒的结构,尽管后一个函数会准确地告诉您时钟的分辨率。

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

      您可能仍需要在循环中多次运行测试函数,以使时钟记录超出其分辨率的任何时间,并且您可能希望循环运行足够多的时间以至少持续一个数量级以上时间比时钟的分辨率。

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

      或者,可以使用相关的 POSIX.1 timer_settime() 调用来设置一个正在运行的定时器,一个信号处理程序来捕获定时器传递的信号,并使用 timer_getoverrun() 来找出队列之间经过了多少时间信号及其最终传递,然后将循环设置为运行,直到计时器关闭,计算设置的时间间隔内的迭代次数,加上溢出。

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

      稍微少见的是可选的 POSIX.1-1999 clockid_tCLOCK_PROCESS_CPUTIME_ID,由来自 &lt;time.h&gt;_POSIX_CPUTIME 的存在表示,它表示调用进程的 CPU 时间时钟,给出表示调用进程的执行时间量的值。 (更罕见的是CLOCK_THREAD_CPUTIME_IDclockid_t 的TCT 选项,由_POSIX_THREAD_CPUTIME 宏表示,它表示CPU 时钟,给出的值表示调用线程的执行时间。)

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

      更糟糕的是,在多处理器系统上,如果您的进程在执行期间碰巧从一个 CPU 迁移到另一个 CPU,那么 CPUTIME 时钟的值可能完全是虚假的。实现这些 CPUTIME 时钟的计时器也可能在不同的 CPU 内核上以不同的速度运行,并且在不同的时间运行,这使它们的含义进一步复杂化。 IE。它们可能与实际挂钟时间无关,而仅表示 CPU 周期数(只要始终使用相对时间并且用户知道执行时间可能会有所不同,这对于基准测试仍然有用取决于外部因素)。更糟糕的是,据报道,在 Linux CPU TimeStampCounter 上,基于 CPUTIME 时钟甚至可以报告进程已休眠的时间。

      如果您的系统有一个运行良好的getrusage() 系统调用,那么它有望为您的进程消耗的每个实际用户和系统时间分别提供一个struct timeval当它运行时。但是,由于这最多会使您回到微秒时钟,因此您需要重复运行测试代码足够多次以获得更准确的时间,在循环之前调用getrusage(),然后再调用一次,并计算差异在给定的时间之间。对于简单的算法,这可能意味着运行它们数百万次,甚至更多。另请注意,在许多系统上,用户时间和系统时间之间的划分有些随意,如果在重复循环中单独检查,其中一个甚至可能出现倒退。但是,如果您的算法没有进行系统调用,那么对时间增量求和仍然应该是您的代码执行的合理总时间。

      顺便说一句,在比较时间值时要小心,不要像@Nim 建议的那样,或者像这样(来自 NetBSD 的 &lt;sys/time.h&gt;):

          #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的变量的值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-23
        • 2012-06-05
        • 1970-01-01
        • 2012-02-20
        • 2014-11-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多