OpenMP 减慢了计算速度
OpenMP slows down computations
我正在尝试使用 OpenMP 并行化一个简单的循环。下面是我的代码:
#include <iostream>
#include <omp.h>
#include <time.h>
#define SIZE 10000000
float calculate_time(clock_t start, clock_t end) {
return (float) ((end - start) / (double) CLOCKS_PER_SEC) * 1000;
}
void openmp_test(double * x, double * y, double * res, int threads){
clock_t start, end;
std::cout << std::endl << "OpenMP, " << threads << " threads" << std::endl;
start = clock();
#pragma omp parallel for num_threads(threads)
for(int i = 0; i < SIZE; i++){
res[i] = x[i] * y[i];
}
end = clock();
for(int i = 1; i < SIZE; i++){
res[0] += res[i];
}
std::cout << "time: " << calculate_time(start, end) << std::endl;
std::cout << "result: " << res[0] << std::endl;
}
int main() {
double *dbl_x = new double[SIZE];
double *dbl_y = new double[SIZE];
double *res = new double[SIZE];
for(int i = 0; i < SIZE; i++){
dbl_x[i] = i % 1000;
dbl_y[i] = i % 1000;
}
openmp_test(dbl_x, dbl_y, res, 1);
openmp_test(dbl_x, dbl_y, res, 1);
openmp_test(dbl_x, dbl_y, res, 2);
openmp_test(dbl_x, dbl_y, res, 4);
openmp_test(dbl_x, dbl_y, res, 8);
delete [] dbl_x;
delete [] dbl_y;
delete [] res;
return 0;
}
我编译如下
g++ -O3 -fopenmp main.cpp -o ompTest
然而,运行 在 Core-i7 上进行测试后,我得到以下结果:
OpenMP,1 个线程
时间:31.468
结果:3.32834e+12
OpenMP,1 个线程
时间:18.663
结果:3.32834e+12
OpenMP,2 个线程
时间:34.393
结果:3.32834e+12
OpenMP,4 个线程
时间:56.31
结果:3.32834e+12
OpenMP,8 个线程
时间:108.54
结果:3.32834e+12
我不明白我做错了什么?为什么 OpenMP 会减慢计算速度?
另外,为什么第一个结果比第二个结果慢得多(均使用 1 个 omp 线程)?
我的测试环境:Core i7-4702MQ CPU @ 2.20GHz, Ubuntu 18.04.2 LTS, g++ 7.4.0.
目前,您创建了线程,但您为它们分配了相同的工作。
我想,你忘记了pragma中的"for",它使线程将循环分成几部分。
#pragma omp parallel for num_threads(threads)
这里至少发生了两件事。
clock()
测量经过的 处理器 时间,这可以看作是对已执行工作量的测量,而你想测量经过的时间墙 时间。见 OpenMP time and clock() calculates two different results.
并行程序中的总处理器时间应多于可比较的串行程序,因为并行化会增加开销。线程越多,开销越大,因此每个添加线程的速度提升会随着线程的增加而降低,甚至可能变为负值。
与您的代码的这个变体相比,它实现了一种更合适的方法来测量经过的墙时间:
float calculate_time(struct timespec start, struct timespec end) {
long long start_nanos = start.tv_sec * 1000000000LL + start.tv_nsec;
long long end_nanos = end.tv_sec * 1000000000LL + end.tv_nsec;
return (end_nanos - start_nanos) * 1e-6f;
}
void openmp_test(double * x, double * y, double * res, int threads){
struct timespec start, end;
std::cout << std::endl << "OpenMP, " << threads << " threads" << std::endl;
clock_gettime(CLOCK_MONOTONIC, &start);
#pragma omp parallel num_threads(threads)
for(int i = 0; i < SIZE; i++){
res[i] = x[i] * y[i];
}
clock_gettime(CLOCK_MONOTONIC, &end);
for(int i = 1; i < SIZE; i++){
res[0] += res[i];
}
std::cout << "time: " << calculate_time(start, end) << std::endl;
std::cout << "result: " << res[0] << std::endl;
}
我的结果是
OpenMP, 1 threads
time: 92.5535
result: 3.32834e+12
OpenMP, 2 threads
time: 56.128
result: 3.32834e+12
OpenMP, 4 threads
time: 59.8112
result: 3.32834e+12
OpenMP, 8 threads
time: 78.9066
result: 3.32834e+12
请注意两个线程的测量时间如何减少了大约一半,但添加更多内核并没有太大改善,最终开始趋向于回到 single-thread 时间。* 这展示了在我的 four-core、eight-hyperthread 机器上同时执行更多工作的竞争效应,以及与协调更多线程相关的开销和资源争用增加。
底线:在任务中投入更多线程并不一定能让你更快地获得结果,而且它很少能让你获得与线程数成比例的加速。
* 全面披露:我 cherry-picked 这些特定结果来自多次运行的结果。所有都显示出相似的趋势,但趋势在这一个中特别明显 - 因此可能被过分强调了。
我正在尝试使用 OpenMP 并行化一个简单的循环。下面是我的代码:
#include <iostream>
#include <omp.h>
#include <time.h>
#define SIZE 10000000
float calculate_time(clock_t start, clock_t end) {
return (float) ((end - start) / (double) CLOCKS_PER_SEC) * 1000;
}
void openmp_test(double * x, double * y, double * res, int threads){
clock_t start, end;
std::cout << std::endl << "OpenMP, " << threads << " threads" << std::endl;
start = clock();
#pragma omp parallel for num_threads(threads)
for(int i = 0; i < SIZE; i++){
res[i] = x[i] * y[i];
}
end = clock();
for(int i = 1; i < SIZE; i++){
res[0] += res[i];
}
std::cout << "time: " << calculate_time(start, end) << std::endl;
std::cout << "result: " << res[0] << std::endl;
}
int main() {
double *dbl_x = new double[SIZE];
double *dbl_y = new double[SIZE];
double *res = new double[SIZE];
for(int i = 0; i < SIZE; i++){
dbl_x[i] = i % 1000;
dbl_y[i] = i % 1000;
}
openmp_test(dbl_x, dbl_y, res, 1);
openmp_test(dbl_x, dbl_y, res, 1);
openmp_test(dbl_x, dbl_y, res, 2);
openmp_test(dbl_x, dbl_y, res, 4);
openmp_test(dbl_x, dbl_y, res, 8);
delete [] dbl_x;
delete [] dbl_y;
delete [] res;
return 0;
}
我编译如下
g++ -O3 -fopenmp main.cpp -o ompTest
然而,运行 在 Core-i7 上进行测试后,我得到以下结果:
OpenMP,1 个线程 时间:31.468 结果:3.32834e+12
OpenMP,1 个线程 时间:18.663 结果:3.32834e+12
OpenMP,2 个线程 时间:34.393 结果:3.32834e+12
OpenMP,4 个线程 时间:56.31 结果:3.32834e+12
OpenMP,8 个线程 时间:108.54 结果:3.32834e+12
我不明白我做错了什么?为什么 OpenMP 会减慢计算速度?
另外,为什么第一个结果比第二个结果慢得多(均使用 1 个 omp 线程)?
我的测试环境:Core i7-4702MQ CPU @ 2.20GHz, Ubuntu 18.04.2 LTS, g++ 7.4.0.
目前,您创建了线程,但您为它们分配了相同的工作。
我想,你忘记了pragma中的"for",它使线程将循环分成几部分。
#pragma omp parallel for num_threads(threads)
这里至少发生了两件事。
clock()
测量经过的 处理器 时间,这可以看作是对已执行工作量的测量,而你想测量经过的时间墙 时间。见 OpenMP time and clock() calculates two different results.并行程序中的总处理器时间应多于可比较的串行程序,因为并行化会增加开销。线程越多,开销越大,因此每个添加线程的速度提升会随着线程的增加而降低,甚至可能变为负值。
与您的代码的这个变体相比,它实现了一种更合适的方法来测量经过的墙时间:
float calculate_time(struct timespec start, struct timespec end) {
long long start_nanos = start.tv_sec * 1000000000LL + start.tv_nsec;
long long end_nanos = end.tv_sec * 1000000000LL + end.tv_nsec;
return (end_nanos - start_nanos) * 1e-6f;
}
void openmp_test(double * x, double * y, double * res, int threads){
struct timespec start, end;
std::cout << std::endl << "OpenMP, " << threads << " threads" << std::endl;
clock_gettime(CLOCK_MONOTONIC, &start);
#pragma omp parallel num_threads(threads)
for(int i = 0; i < SIZE; i++){
res[i] = x[i] * y[i];
}
clock_gettime(CLOCK_MONOTONIC, &end);
for(int i = 1; i < SIZE; i++){
res[0] += res[i];
}
std::cout << "time: " << calculate_time(start, end) << std::endl;
std::cout << "result: " << res[0] << std::endl;
}
我的结果是
OpenMP, 1 threads time: 92.5535 result: 3.32834e+12 OpenMP, 2 threads time: 56.128 result: 3.32834e+12 OpenMP, 4 threads time: 59.8112 result: 3.32834e+12 OpenMP, 8 threads time: 78.9066 result: 3.32834e+12
请注意两个线程的测量时间如何减少了大约一半,但添加更多内核并没有太大改善,最终开始趋向于回到 single-thread 时间。* 这展示了在我的 four-core、eight-hyperthread 机器上同时执行更多工作的竞争效应,以及与协调更多线程相关的开销和资源争用增加。
底线:在任务中投入更多线程并不一定能让你更快地获得结果,而且它很少能让你获得与线程数成比例的加速。
* 全面披露:我 cherry-picked 这些特定结果来自多次运行的结果。所有都显示出相似的趋势,但趋势在这一个中特别明显 - 因此可能被过分强调了。