这是比较C++和Python CPU时间的有效方法吗?

3 投票
2 回答
890 浏览
提问于 2025-04-18 05:34

我想比较一下用C++和Python写的代码在Linux上运行时的CPU时间。下面的方法能否给出一个“公平”的比较呢?

Python

我使用了resource模块

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能让我得到更好的分辨率,但我不确定这有多大的意义。设置优化标志确实会带来很大的不同。

2 个回答

1

设置优化标志确实会带来很大的不同。

C++是一种非常适合进行优化编译的语言,尤其是当代码使用了C++标准库中的容器和迭代器时。比如,一个简单的++iterator,在没有优化编译时可能会变成一大串函数调用,而启用优化后,它就只会变成一两条汇编指令。

我知道编译器会如何处理你的测试代码。任何一个不错的优化编译器都会让这个for (int i=0; i<N; i++) continue;循环消失。这里的“就像”规则在起作用。这个循环什么都不做,所以编译器可以把它当作根本不存在。

当我观察一个可能占用CPU资源的程序时,我会写一个简单的驱动程序(在一个单独的文件中),让可疑的函数调用很多次,有时甚至是非常多次。我会把需要测试的功能编译时开启优化,但驱动程序则关闭优化。我不想让一个过于聪明的优化编译器看到那100,000次对function_to_be_tested()的调用可以从循环中提取出来,然后进一步优化掉这个循环。

在开始计时和停止计时之间多次调用测试函数是有很多合理的原因的。这就是为什么Python有timeit模块的原因。

1

文档中提到:

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

这说得有点模糊。CLOCK_PER_SEC 设置为 10^6,这里的“近似”意味着分辨率不高,而不是说当前时钟的速度快了1000倍,结果是四舍五入的。虽然这可能不是一个很专业的术语,但用在这里是合适的。我测试的地方实际分辨率大约是100Hz,也就是0.01秒。这种情况已经持续了很多年。这里可以参考一下这个链接 http://www.guyrutenberg.com/2007/09/10/resolution-problems-in-clock/

接下来文档提到:“在兼容POSIX的系统上,使用 clock_gettime 和时钟ID CLOCK_PROCESS_CPUTIME_ID 可以获得更好的分辨率。

所以:

  1. 这只是CPU时间。但如果有两个线程,那就是2倍的CPU时间。可以参考cppreference上的例子。

  2. 如上所述,这根本不适合进行精细的测量。你已经接近它的准确性了。

  3. 我认为测量实际的墙钟时间是唯一合理的选择,但这只是我个人的看法。特别是在多线程应用和多进程的情况下。否则,systemuser的结果应该是相似的。

补充说明:对于第三点,这当然适用于计算任务。如果你的进程使用了sleep或者将执行权交还给系统,测量CPU时间可能更可行。还有关于clock分辨率的问题,确实...不太好。是的,但公平地说,可以认为你不应该测量这么短的计算时间。虽然我觉得这很遗憾,但如果你测量几秒钟的时间,我想这样也可以。我个人会使用其他可用的工具。

撰写回答