如何在 Linux 中的 C++ 中计时操作时处理缓存

Posted

技术标签:

【中文标题】如何在 Linux 中的 C++ 中计时操作时处理缓存【英文标题】:How to handle caching while timing an operating in C++ in linux 【发布时间】:2013-10-19 17:40:49 【问题描述】:

我必须为 clock_gettime() 函数计时以估计和分析其他操作,而且它是家庭作业,所以我不能使用分析器并且必须编写自己的代码。

我的做法如下:

clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&begin);

for(int i=0;i<=n;i++)
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);

cout<<(end.tv_nsec-begin.tv_nsec)/n; //time per clock_gettime()

问题是当n=100时,输出为:370.63 ns,当n=100000时,输出:330 ns,当n=1000000时,输出:260 ns,n=10000000,输出:55 ns,... .不断减少。

我知道这是由于指令缓存而发生的,但我不知道如何在分析中处理这个问题。因为例如当我使用 gettime 估计函数调用的时间时,我怎么知道该 gettime 自己使用了多少时间?

对所有这些值取加权平均值是个好主意吗? (我可以运行我想要的相同次数的操作,取其加权平均值,减去 gettime 的加权平均值并获得对操作的良好估计,而不考虑缓存?)

欢迎提出任何建议。

提前谢谢你。

【问题讨论】:

好吧,每当您调用 clock_gettime() 来分析其他内容时,它都不会被缓存(通常?),因此上下文之外的调用可以用于分析其他内容......如果我让自己清除。 好点。知道如何在没有任何缓存的情况下禁用指令缓存到时间 get_time 吗?我认为仅使用低 n 的值并不是一个好主意。 【参考方案1】:

计算时差时:(end.tv_nsec-begin.tv_nsec)/n

您只考虑了经过时间的纳秒部分。您还必须考虑秒数,因为 tv_nsec 字段仅反映秒的小数部分:

int64_t end_ns = ((int64_t)end.tv_sec * 1000000000) + end.tv_nsec;
int64_t begin_ns = ((int64_t)begin.tv_sec * 1000000000) + begin.tv_nsec;
int64_t elapsed_ns = end_ns - begin_ns;

实际上,使用您当前的代码,当end 的纳秒部分已经回绕并且小于begin 的纳秒部分时,您有时会得到否定的结果。

解决这个问题,您将能够观察到更加一致的结果。


编辑:为了完整起见,这是我用于测试的代码,它得到了非常一致的结果(每次调用在 280 到 300ns 之间,无论我使用多少次迭代):

int main() 
  const int loops = 100000000;

  struct timespec begin;
  struct timespec end;
  clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &begin);

  for(int i = 0; i < loops; i++)
      clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);
  clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);

  int64_t end_ns = ((int64_t)end.tv_sec * 1000000000) + end.tv_nsec;
  int64_t begin_ns = ((int64_t)begin.tv_sec * 1000000000) + begin.tv_nsec;
  int64_t elapsed_ns = end_ns - begin_ns;
  int64_t ns_per_call = elapsed_ns / loops;
  std::cout << ns_per_call << std::endl;

【讨论】:

纳秒字段不会回绕,int_64的大小为:2^64-1,约为1.8 x 10^19。因此,纳秒环绕的秒数将是 (1.8x10^19)/(10^9) ~ 10^10 秒。那里没有问题。谢谢你提醒我要考虑秒数,那是个错误。我假设秒和纳秒字段是独立的。 顺便说一句,结果现在看起来像这样:n=100,时间:374ns,n=10000,时间:363ns,n=100,000 时间:241ns,n=10,00,000 时间:153ns,n= 1,00,00,000 次:135ns,进一步增加 n 它仍然保持在 135 左右(完全缓存?) 我所说的“环绕”是指tv_nsec 仅限于 0 到 999999999 (10^9 - 1) 之间的值。当整整一秒过去时,tv_sec 增加,tv_nsec 回到零。所以,是的,你可以拥有end.tv_nsec &lt; begin.tv_nsec,这会给你的原始计算带来负值。至于根据迭代次数从 375ns 演变到 135ns 的时间,一旦我在我的答案中使用了修复程序,我就无法重现:无论迭代次数如何,我都会在 280-300ns 左右得到一致的结果。 我添加了我使用的代码,以便您可以与您的进行比较。同样,这给了我非常一致的结果,与您观察到的相去甚远。 这很奇怪。即使按原样运行您的代码,我也会得到值:131 或 132 ns。当我将代码中的 n 减少到 100,000 时,我得到大约 250。

以上是关于如何在 Linux 中的 C++ 中计时操作时处理缓存的主要内容,如果未能解决你的问题,请参考以下文章

当类向导损坏时,将计时器 (WM_TIMER) 处理程序添加到 Visual C++ 6.0?

如何在 C++ 和 FLTK 中实现倒计时时钟?

如何在 C++ 中倒计时 [重复]

如何使用 C++ 代码中的 BTRFS 写入时复制?

[C++基础] 函数技巧 - 计时函数

在 C++ 中使用超时处理程序实现计时器