【问题标题】:How can I benchmark C code easily?如何轻松地对 C 代码进行基准测试?
【发布时间】:2011-01-21 22:17:45
【问题描述】:

是否有一个简单的库来对执行部分 C 代码所需的时间进行基准测试?我想要的是这样的:

int main(){
    benchmarkBegin(0);
    //Do work
    double elapsedMS = benchmarkEnd(0);

    benchmarkBegin(1)
    //Do some more work
    double elapsedMS2 = benchmarkEnd(1);

    double speedup = benchmarkSpeedup(elapsedMS, elapsedMS2); //Calculates relative speedup
}

如果该库允许您进行多次运行、平均它们并计算时间上的差异,那就太好了!

【问题讨论】:

  • 好问题,这对我帮助很大。
  • 程序内计时的替代方案:stackoverflow.com/questions/7456146/…
  • 在同一个程序中做两次类似的工作可能会让编译器在它们之间进行优化。构建多个可执行文件,每个微基准测试一个单一的实现策略更安全(但更麻烦)。将程序的整个运行时间作为基准可以轻松比较来自perf stat 的性能计数器结果,这意味着您可以使用time ./a.out 之类的外部计时内容,而不是在您的 C 中包含计时代码。也就是说,程序中的时序代码可以让您避免时序初始化代码。一个结果的多个结果更简单。

标签: c benchmarking


【解决方案1】:

使用time.h中定义的函数clock()

startTime = (float)clock()/CLOCKS_PER_SEC;

/* Do work */

endTime = (float)clock()/CLOCKS_PER_SEC;

timeElapsed = endTime - startTime;

【讨论】:

  • 这应该是公认的答案,而不是 Windows 特定的答案!
  • clock() 返回 CPU 时间而不是挂钟时间,如果您在基准测试时有多个线程执行代码,这可能会让您感到惊讶。
  • @neevek 但是如果你只有一个线程,它会导致正确的结果,因为如果你使用挂钟时间,你的系统负载会影响基准测试结果。如果您的系统非常忙于执行后台任务,那么您获得的基准测试结果会比使用挂钟时间时更差,但使用 CPU 时间时您将获得相同的结果。
  • 精度怎么样?通过执行基准代码 N 次并将测量的时间除以 N,我们提高了精度。我们如何确定 N 和精度?
【解决方案2】:

基本上,您只需要一个高分辨率计时器。经过的时间当然只是时间上的差异,加速比是通过除以每个任务的时间来计算的。我已经包含了一个高分辨率计时器的代码,它至少可以在 windows 和 unix 上运行。

#ifdef WIN32

#include <windows.h>
double get_time()
{
    LARGE_INTEGER t, f;
    QueryPerformanceCounter(&t);
    QueryPerformanceFrequency(&f);
    return (double)t.QuadPart/(double)f.QuadPart;
}

#else

#include <sys/time.h>
#include <sys/resource.h>

double get_time()
{
    struct timeval t;
    struct timezone tzp;
    gettimeofday(&t, &tzp);
    return t.tv_sec + t.tv_usec*1e-6;
}

#endif

【讨论】:

  • 挂钟时间(由gettimeofday 返回)可能没那么有用 - clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ...) 通常是那里想要的。
  • @caf:使用很少 CPU 时间但花费大量时间进行阻塞 I/O 或等待异步 I/O 的程序仍然会被用户认为很慢。 CPU 时间和挂钟时间都很重要。
  • 是的,这就是为什么我用黄鼠狼词“可能”和“经常”来限定我的评论;)顺便说一句,如果希望挂钟时间,那么@987654324 @ 是一个更好的选择,因为与 gettimeofday 不同,它不会在时间间隔内受到系统时钟变化的影响。
  • 在我的典型用法中,我只关心挂钟时间,因为我正在做资源密集型的事情。我不确定clock_gettime 是如何与多线程一起工作的,但这似乎是挂钟时间是唯一准确度量的领域。
  • 顺便说一句,QueryPerformanceFrequency 不应该真的每次都被调用。
【解决方案3】:

轻松对 C 代码进行基准测试

#include <time.h>

int main(void) {
  clock_t start_time = clock();

  // code or function to benchmark

  double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC;
  printf("Done in %f seconds\n", elapsed_time);
}

多线程 C 代码的简单基准测试

如果您想对多线程程序进行基准测试,您首先需要仔细查看clock

说明

clock() 函数返回处理器时间的近似值 程序使用。

返回值

返回的值是目前为止作为clock_t使用的CPU时间;到 获取使用的秒数,除以 CLOCKS_PER_SEC。如果 使用的处理器时间不可用或其值不能 表示,函数返回值 (clock_t)(-1)

因此,将您的 elapsed_time 除以线程数以获得函数的执行时间非常重要:

#include <time.h>
#include <omp.h>

#define THREADS_NB omp_get_max_threads()

#pragma omp parallel for private(i) num_threads(THREADS_NB)
clock_t start_time = clock();

// code or function to benchmark

double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC;
printf("Done in %f seconds\n", elapsed_time / THREADS_NB); // divide by THREADS_NB!

示例

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <omp.h>

#define N 20000
#define THREADS_NB omp_get_max_threads()

void init_arrays(double *a, double *b) {
  memset(a, 0, sizeof(a));
  memset(b, 0, sizeof(b));
  for (int i = 0; i < N; i++) {
    a[i] += 1.0;
    b[i] += 1.0;
  }
}

double func2(double i, double j) {
  double res = 0.0;

  while (i / j > 0.0) {
    res += i / j;
    i -= 0.1;
    j -= 0.000003;
  }
  return res;
}

double single_thread(double *a, double *b) {
  double res = 0;
  int i, j;
  for (i = 0; i < N; i++) {
    for (j = 0; j < N; j++) {
      if (i == j) continue;
      res += func2(a[i], b[j]);
    }
  }
  return res;
}

double multi_threads(double *a, double *b) {
  double res = 0;
  int i, j;
  #pragma omp parallel for private(j) num_threads(THREADS_NB) reduction(+:res)
  for (i = 0; i < N; i++) {
    for (j = 0; j < N; j++) {
      if (i == j) continue;
      res += func2(a[i], b[j]);
    }
  }
  return res;
}

int main(void) {
  double *a, *b;
  a = (double *)calloc(N, sizeof(double));
  b = (double *)calloc(N, sizeof(double));
  init_arrays(a, b);

  clock_t start_time = clock();
  double res = single_thread(a, b);
  double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC;
  printf("Default:  Done with %f in %f sd\n", res, elapsed_time);

  start_time = clock();
  res = multi_threads(a, b);
  elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC;
  printf("With OMP: Done with %f in %f sd\n", res, elapsed_time / THREADS_NB);
}

编译:

gcc -O3 multithread_benchmark.c -fopenmp && time ./a.out

输出:

Default:  Done with 2199909813.614555 in 4.909633 sd
With OMP: Done with 2199909799.377532 in 1.708831 sd

real    0m6.703s (from time function)

【讨论】:

  • 你不是假设所有线程都可以一直充分利用所有内核吗?因此,如果有任何同步开销,您就低估了实时量。如果您想要实时,请询问实时(使用clock_gettime),并在空闲系统上进行测试。然后,您可以 比较 与在该实时量期间使用的 CPU 时间的总 CPU 秒数。或者最小化启动开销并让基准测试重复到足以支配整个运行时,perf stat 你的整个程序会为你做这一切,包括显示task-clock 和使用的 3.800 个 CPU 等等。
  • 我不得不做出这个假设,因为没有办法在不减慢程序速度的情况下知道当前活动线程的数量;)更重要的是要有一个公平的估计并且它有效,它是没有的表示精确。
  • 如果你坚持推断实时而不是通过一些稍微不那么便携的高 rez 时间源直接测量它,你只有“不得不”。我不推荐这个;就像我说的,使用适当的实时时钟,而不是完全推断。您可能会自欺欺人并隐藏任何串行或不太并行的阶段,以解决无法完美分割成具有均匀工作量的均匀大小的块的问题。 (存在不同的 OpenMP 调度选项来处理此问题,例如动态与静态。)
  • memset(a, 0, sizeof(a)) 不正确。你应该写 memset(a, 0, sizeof(*a) * N)N 应该作为参数传递,尽管如果 N 是一个变量,编译器会更难并行化代码。
  • 您对 OMP 计时的方法是有问题的:要么您对单个执行线程的性能感兴趣并且您可以通过不并行化代码生成来获得它,或者您想要评估OMP 代码生成器,您应该报告挂钟计时请求和使用的实际线程数。仅仅除以那个数字就是删除一个重要的信息。您实际上应该将时间乘以实际的线程数,然后与单线程时间进行比较,看看 OMP 是否有效甚至有用。
【解决方案4】:

在 POSIX 中,尝试getrusage。相关参数为 RUSAGE_SELF,相关字段为 ru_utime.tv_sec 和 ru_utime.tv_usec。

【讨论】:

  • 请注意,这在许多使用procfs 的UNIX 操作系统中并未真正实现。 (Linux、Solaris)
【解决方案5】:

可能有现有的实用程序可以帮助解决此问题,但我怀疑大多数会使用某种采样或可能的注入。但是要让特定的代码部分计时,您可能必须添加对计时器的调用,就像您在示例中显示的那样。如果您使用的是 Windows,则高性能计时器可以工作。我answered a similar question 并展示了可以做到这一点的示例代码。 Linux 也有类似的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-10
    • 2020-11-25
    • 2014-09-20
    • 2018-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多