目录

测量多线程每个线程的运行时间

# 1. 问题

在一个并发计算的过程中怎么知道每个线程的运行时间?

一个容易想到的方法是每个线程的开始和结束获取一下系统时间,代码如下:

using clock = std::chrono::high_resolution_clock;
#pragma omp parallel
{
    int thread_id = omp_get_thread_num();
    auto start = clock::now();
    calc();
    auto duration = clock::now() - start;
    benchmark_result[thread_id] = duration.count();
}

但是有一个问题,并发访问系统时间 clock::now() 会有竞争,开销很大。如果线程数很多(几十或上百),那么第一个获取系统时间和最后一个获取系统时间至少相差几微秒(这个时间不好测量,估计可能会有上百微秒)。

如果我们的测量对象(calc 函数)也是微秒级别的,那就会出现有的线程跑完了,有的线程还没开始的情况。

# 2. 开始时间的处理

一个方法是把开始时间放到并行外面去,所有线程公用一个开始时间。如下:

double benchmark_result[1145];
using clock = std::chrono::high_resolution_clock;
auto start = clock::now();
#pragma omp parallel
{
    int thread_id = omp_get_thread_num();
    calc();
    auto duration = clock::now() - start;
    benchmark_result[thread_id] = duration.count();
}

另一个方法是 0 号线程获取时间,然后做一次线程同步,其他线程公用这个时间(这个线程同步 #pragma omp barrier 可以用用户态的自旋来代替,减少开销):

double benchmark_result[1145];
using clock = std::chrono::high_resolution_clock;
decltype(clock::now()) start;
#pragma omp parallel
{
    int thread_id = omp_get_thread_num();
    if (thread_id == 0) {
        start = clock::now();
    }
#pragma omp barrier
    calc();
    auto duration = clock::now() - start;
    benchmark_result[thread_id] = duration.count();
}

虽然线程同步时间也是微秒级,但是好在基本可以控制线程同时开始。

# 3. 线程内多次运行取平均

由于结束的获取系统时间的竞争不可避免,可以多次运行取平均来稀释掉这个时间:

double benchmark_result[1145];
using clock = std::chrono::high_resolution_clock;
decltype(clock::now()) start;
#pragma omp parallel
{
    int thread_id = omp_get_thread_num();
    if (thread_id == 0) {
        start = clock::now();
    }
#pragma omp barrier
    for (int i = 0; i < 10000; i++) { calc(); }
    auto duration = clock::now() - start;
    benchmark_result[thread_id] = duration.count() / 10000.0;
}

# 4. 最终问题

如果运行时间是微秒级别,且不能多次运行取平均(只能跑一次),该怎么做?

目前无解。