【问题标题】:How to make sure the call is not optimised away when measuring time?如何确保在测量时间时呼叫没有被优化掉?
【发布时间】:2015-09-04 19:04:21
【问题描述】:

我写了一个函数模板来测量时间:

#include <ctime>
template <typename FUNCTION,typename INPUT,int N>
double measureTime(FUNCTION f,INPUT inp){
  // double x;
  double duration = 0;
  clock_t begin = clock();
  for (int i=0;i<N;i++){
      // x = f(inp);  
      f(inp);
  }
  clock_t end = clock();
  // std::cout << x << std::endl;
  return double(end-begin) / CLOCKS_PER_SEC;
}

我是这样使用它的:

#include <iostream>
typedef std::vector<double> DVect;
double passValue(DVect a){
    double sum = 0;
    for (int i=0;i<a.size();i++){sum += sum+a[i];}
    return sum;
}
typedef double (*passValue_type)(DVect);

int main(int argc, char *argv[]) {
    const int N = 1000;
    const int size = 10000;
    std::vector<double> v(size,0);
    std::cout << measureTime<passValue_type,DVect,N>(passValue,v) << std::endl;
}

目的是可靠地测量不同功能的 CPU 时间,例如值传递与引用传递。实际上,它似乎工作得很好,但是,有时结果时间太短而无法测量,结果我只得到 0。为了确保调用该函数,我打印了调用的结果(参见上面代码中的 cmets)。这是我想避免的,我想让模板尽可能简单,所以我的问题是:

我怎样才能确保函数真的被调用并且没有被优化掉(因为没有使用返回值)?

【问题讨论】:

  • 您可以尝试应用一些副作用,例如更新用作返回值的静态局部变量。当然,如果你真的想在多线程应用程序中使用返回的值,那是行不通的。
  • 也许这会起作用:stackoverflow.com/questions/7083482/…
  • 在我看来,您的 measureTime 函数会告诉您代码实际执行所需的时间。如果您担心按值传递太慢,那么您应该对您关心的实际代码进行基准测试,您相当确定它不会被优化,并且您不会遇到这个问题。
  • @Brian 我对此并不关心,我只是想进行一些实验以获得对差异的定量感觉,但是我的测试用例要么需要很长的时间才能通过价值,要么很少通过参考测量。

标签: c++ performance optimization


【解决方案1】:

我通常会这样做:

#include <ctime>

template <typename FUNCTION,typename INPUT,int N>
double measureTime(FUNCTION f,INPUT inp){
  double x = 0;
  double duration = 0;
  clock_t begin = clock();
  for (int i=0;i<N;i++){
      x += f(inp);  
  }
  clock_t end = clock();
  std::cout << x << std::endl;
  // or if (x < 0) cout << x; or similar.
  // such that it doesn't ACTUALLY print anything.
  return double(end-begin) / CLOCKS_PER_SEC;
}

以上假设f 实际上做了一些编译器无法弄清楚如何简化的不平凡的事情。如果freturn 6;,那么编译器会将其转换为x = 6 * N;,这样运行时间确实很短。

如果你想能够使用“任何”功能,你将不得不做一些更聪明的事情:

template <typename FUNCTION,typename INPUT,int N, typename RET>
double measureTime(FUNCTION f,INPUT inp){
  RET x = 0;
  double duration = 0;
  clock_t begin = clock();
  for (int i=0;i<N;i++){
      x += f(inp);  
  }
  clock_t end = clock();
  std::cout << x << std::endl;
  return double(end-begin) / CLOCKS_PER_SEC;
}

template <typename FUNCTION,typename INPUT,int N, void>
double measureTime(FUNCTION f,INPUT inp){
  clock_t begin = clock();
  for (int i=0;i<N;i++){
      f(inp);  
  }
  clock_t end = clock();
  return double(end-begin) / CLOCKS_PER_SEC;
}

[我还没有真正编译上面的代码,所以它可能有一些小缺陷,但作为一个概念它应该可以工作]。

由于任何有意义的void 函数都必须做一些影响周围世界的事情(输出到流、更改全局变量或调用某个系统调用),因此它不会被消除。当然,调用空函数或类似函数很可能会造成麻烦。

另一种方法,假设您不关心不内联调用是实际将被测函数放在单独的文件中,而不是让编译器从测量时间的代码中“看到”该函数 [而不是使用-flto 允许它在链接时内联函数] - 这样,编译器就无法知道被测函数在做什么,也不会消除调用。

应该注意的是,除了“使编译器不可能知道函数的结果是什么”(例如使用 random /externally sourced input),或“不要让编译器知道函数的作用”。

【讨论】:

  • 是的,这是可行的,但这是我想避免的。该模板应该适用于任何返回类型,我认为这是最简单的,当返回值被简单地忽略时
  • 请注意,聪明的编译器可能仍然能够对其进行常量折叠 (I've seen clang doing something like that)。
  • 然后添加函数的返回类型,并专门处理void 的情况——因为void 函数必须“做其他事情”[除非它实际上根本没有做任何事情]。我将编辑一个示例。
  • 我已经接近接受这个答案了,但我还有一个小疑问:如果返回类型没有operator+怎么办?
  • 再一次,你可能也必须专门化那个 - 但问题是你真的需要使用结果 - 如果 N 足够小,你可以制作一个本地数组例如存储函数的输出 [然后使用所有输出] - 或者总结 x.size() 如果它是某个容器。几乎不可能涵盖所有可能发生的情况 - 如果它是不可复制的回报等等,等等。我认为没有一种方法适用于所有事情。
【解决方案2】:

无内联:确保函数调用和函数定义位于单独的编译单元(即 cpp 文件)中,然后在您的构建中禁用 link-time optimization

在这种情况下,由于编译单元在 C++ 中的工作方式,编译器将无法内联您的函数调用。此外,编译器将无法完全删除调用。事实上,在优化调用的那一刻,它对你的函数一无所知(除了签名)。

内联:如果您想使用内联函数调用来测量时间,上述简单方法将不起作用。在这种情况下,您必须确保:对于函数内的每个操作,都有一些依赖于它的 observable behavior。例如,您可以将结果写入volatile 变量,或计算结果的一些总和/哈希并将其打印到标准输出。

【讨论】:

    【解决方案3】:

    许多编译器都有扩展来禁用函数的内联。对于 gcc,它是__attribute__((noinline)),例如:

    __attribute__((noinline)) void foo() { ... }
    

    Boost 提供了一个可移植的BOOST_NOINLINE 宏。

    【讨论】:

    • 可以看到为GCC/MSVC编译器定义的宏here
    猜你喜欢
    • 2019-12-15
    • 1970-01-01
    • 2015-11-20
    • 1970-01-01
    • 1970-01-01
    • 2014-05-29
    • 1970-01-01
    • 2012-06-30
    • 1970-01-01
    相关资源
    最近更新 更多