【发布时间】:2020-09-17 07:48:31
【问题描述】:
我正在尝试使用 OpenMP 对我实现的数据结构的速度进行基准测试。但是,我似乎犯了一个根本性的错误:无论我尝试对什么操作进行基准测试,吞吐量都会随着线程数的增加而减少而不是增加。 在下面您可以看到尝试对 for 循环的速度进行基准测试的代码,因此我希望它可以(在某种程度上)随线程数线性扩展,但它不会(在有和没有的双核笔记本电脑上编译 - g++ 上的 O3 标志与 c++11)。
#include <omp.h>
#include <atomic>
#include <chrono>
#include <iostream>
thread_local const int OPS = 10000;
thread_local const int TIMES = 200;
double get_tp(int THREADS)
{
double threadtime[THREADS] = {0};
//Repeat the test many times
for(int iteration = 0; iteration < TIMES; iteration++)
{
#pragma omp parallel num_threads(THREADS)
{
double start, stop;
int loc_ops = OPS/float(THREADS);
int t = omp_get_thread_num();
//Force all threads to start at the same time
#pragma omp barrier
start = omp_get_wtime();
//Do a certain kind of operations loc_ops times
for(int i = 0; i < loc_ops; i++)
{
//Here I would put the operations to benchmark
//in this case a boring for loop
int x = 0;
for(int j = 0; j < 1000; j++)
x++;
}
stop = omp_get_wtime();
threadtime[t] += stop-start;
}
}
double total_time = 0;
std::cout << "\nThread times: ";
for(int i = 0; i < THREADS; i++)
{
total_time += threadtime[i];
std::cout << threadtime[i] << ", ";
}
std::cout << "\nTotal time: " << total_time << "\n";
double mopss = float(OPS)*TIMES/total_time;
return mopss;
}
int main()
{
std::cout << "\n1 " << get_tp(1) << "ops/s\n";
std::cout << "\n2 " << get_tp(2) << "ops/s\n";
std::cout << "\n4 " << get_tp(4) << "ops/s\n";
std::cout << "\n8 " << get_tp(8) << "ops/s\n";
}
在双核上使用 -O3 的输出,因此我们预计 2 个线程后吞吐量不会增加,但当从 1 个线程变为 2 个线程时它甚至不会增加,它会减少 50%:
1 Thread
Thread times: 7.411e-06,
Total time: 7.411e-06
2.69869e+11 ops/s
2 Threads
Thread times: 7.36701e-06, 7.38301e-06,
Total time: 1.475e-05
1.35593e+11ops/s
4 Threads
Thread times: 7.44301e-06, 8.31901e-06, 8.34001e-06, 7.498e-06,
Total time: 3.16e-05
6.32911e+10ops/s
8 Threads
Thread times: 7.885e-06, 8.18899e-06, 9.001e-06, 7.838e-06, 7.75799e-06, 7.783e-06, 8.349e-06, 8.855e-06,
Total time: 6.5658e-05
3.04609e+10ops/s
为确保编译器不会删除循环,我还尝试在测量时间后输出“x”,据我所知问题仍然存在。我还在具有更多内核的机器上尝试了代码,它的行为非常相似。如果没有 -O3,吞吐量也不会扩展。所以我的基准测试方式显然有问题。我希望你能帮助我。
【问题讨论】:
-
"为了确保编译器不会去掉循环,我也试过在测量时间后输出"x"" -- 那并不能保证编译器不删除循环。编译器可以简单地评估
x的最终值,并用该计算替换整个循环。在从基准测试中得出任何结论之前,请查看代码的汇编输出以验证循环是否仍然存在。您可能正在对创建线程的开销进行基准测试,因为每个线程都没有完成任何实质性工作。 -
我正在查看汇编代码,但我无法理解真正发生的事情,因此将某些内容放入循环中以使其不会得到优化似乎是一个非常好的主意。如果我将 std::rand() 放入其中,结果会更糟,因为 2 个线程的吞吐量约为 1 个线程的吞吐量的 2%。但是,std::rand 是 not necessarily thread 安全的,但我不确定这是否仅涉及其正确性或速度。
-
使用
rand()会因为不断的缓存失效而导致严重的速度损失。要在多线程应用程序中处理随机数,请使用drand48_rfamily 中的函数。它们都将 PRNG 状态作为附加参数。
标签: c++ multithreading concurrency openmp microbenchmark