3

我有兴趣比较一些 C++ 与 Python(在 Linux 上运行)编写的代码部分的 CPU 时间。以下方法会在两者之间产生“公平”的比较吗?

Python

使用资源模块

import resource
def cpu_time():
    return resource.getrusage(resource.RUSAGE_SELF)[0]+\ # time in user mode
        resource.getrusage(resource.RUSAGE_SELF)[1] # time in system mode

这允许像这样的时间:

def timefunc( func ):
    start=cpu_time()
    func()
    return (cpu_time()-start)

然后我测试如下:

def f():
    for i in range(int(1e6)):
        pass

avg = 0
for k in range(10):
    avg += timefunc( f ) / 10.0
print avg
=> 0.002199700000000071

C++

使用ctime库:

#include <ctime>
#include <iostream>

int main() {
    double avg = 0.0;
    int N = (int) 1e6;
    for (int k=0; k<10; k++) {
        clock_t start;
        start = clock();
        for (int i=0; i<N; i++) continue;
        avg += (double)(clock()-start) / 10.0 / CLOCKS_PER_SEC;
    }
    std::cout << avg << '\n';
    return 0;
}

产生0.002

关注点:

  1. 我读过 C++clock()测量 CPU 时间,这是我所追求的,但我似乎无法找到它是否包括用户和系统时间。
  2. C++ 的结果不那么精确。这是为什么?
  3. 如前所述,比较的总体公平性。

更新

根据 David 在评论中的建议更新了 c++ 代码:

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

int main() {
    double avg = 0.0;
    int N = (int) 1e6;
    int tally = 0;

    struct rusage usage;
    struct timeval ustart, ustop, sstart, sstop;

    getrusage(RUSAGE_SELF, &usage);
    ustart = usage.ru_utime;
    sstart = usage.ru_stime;

    for (int k=0; k<10; k++) {
        ustart = usage.ru_utime;
        sstart = usage.ru_stime;

        for (int i=0; i<N; i++) continue;

        getrusage(RUSAGE_SELF, &usage);
        ustop = usage.ru_utime;
        sstop = usage.ru_stime;

        avg += (
            (ustop.tv_sec+ustop.tv_usec/1e6+
            sstop.tv_sec+sstop.tv_usec/1e6)
            -
            (ustart.tv_sec+ustart.tv_usec/1e6+
            sstart.tv_sec+sstart.tv_usec/1e6)
        ) / 10.0; 
    }

    std::cout << avg << '\n';

    return 0;
}

跑步:

g++ -O0 cpptimes.cpp ; ./a.out
=> 0.0020996
g++ -O1 cpptimes.cpp ; ./a.out
=> 0

所以我想getrusage可以让我得到更好的分辨率,但我不确定我应该读多少。设置优化标志肯定会产生很大的不同。

4

2 回答 2

1

设置优化标志肯定会产生很大的不同。

C++ 是一种需要编译优化的语言,特别是如果有问题的代码使用 C++ 标准库中的容器和迭代器。当启用优化时,一个简单++iterator的函数从编译时未优化编译的大小合适的函数调用链缩减为一个或两个汇编语句。

也就是说,我知道编译器会对你的测试代码做什么。任何体面的优化编译器都会使该for (int i=0; i<N; i++) continue;循环消失。这是工作中的假设规则。该循环什么都不做,因此编译器可以自由地将其视为它甚至不存在。

当我查看可疑 CPU 占用者的 CPU 行为时,我编写了一个简单的驱动程序(在单独的文件中),它多次调用可疑函数,有时调用次数非常多。我在启用优化的情况下编译要测试的功能,但我在禁用优化的情况下编译驱动程序。我不希望一个过于聪明的优化编译器看到这 100,000 次调用function_to_be_tested()可以被拉出循环,然后进一步优化循环。

在单次调用启动定时器和停止定时器之间多次调用测试函数有很多充分的理由。这就是python有timeit模块的原因。

于 2014-05-06T20:11:58.730 回答
1

文档说:

"返回自与程序执行相关的实现定义时代开始以来进程使用的近似处理器时间。要将结果值转换为秒,请将其除以 CLOCKS_PER_SEC。 "

这很模糊。CLOCK_PER_SEC设置为10^6并且近似值表示分辨率较差,而不是当前时钟速度快 1000 并且结果被四舍五入。这可能不是一个非常专业的术语,但它是合适的。我测试的所有地方的实际分辨率约为 100Hz = 0,01s。多年来一直如此。请注意此处的日期http://www.guyrutenberg.com/2007/09/10/resolution-problems-in-clock/

然后文档如下:“在与 POSIX 兼容的系统上,时钟 ID 为 CLOCK_PROCESS_CPUTIME_ID 的clock_gettime 提供了更好的分辨率。

所以:

  1. 它只是 CPU 时间。但是 2 个线程 = 2*CPU 时间。请参阅 cppreference 上的示例。

  2. 如上所述,它根本不适合细粒度测量。你正处于其准确性的边缘。

  3. IMO测量挂钟是唯一明智的事情,但这是一个相当个人的观点。特别是对于多线程应用程序和一般的多处理。否则system+的结果user应该是相似的。

编辑:在 3。这当然适用于计算任务。如果您的进程使用sleep或放弃执行回系统,则测量 CPU 时间可能更可行。另外关于clock分辨率是错误的评论......不好。确实如此,但公平地说,你不应该测量这么短的计算。IMO 它太糟糕了,但如果你在几秒钟内测量时间,我猜它很好。我会亲自使用其他可用的工具。

于 2014-05-06T17:49:04.900 回答