我正尝试在数组中查找元素的总和,如下所示。但是,令人惊讶的是,OpenMP实现比顺序实现慢。我尝试了堆分配和堆栈分配的数组,并得到了相似的结果。任何帮助是极大的赞赏。
#include <iostream>
#include <omp.h>
int main() {
int N = 10000;
int * ary = new int[N];
for (int i = 0; i < N; i++) { input_file >> ary[i]; }
int sum = 0;
clock_t begin = clock();
for (int i = 0; i < N; i++) { sum += ary[i]; }
clock_t end = clock();
cout << sum;
double elapsed_time = double(end - begin) / CLOCKS_PER_SEC;
sum = 0;
begin = clock();
#pragma omp parallel
{
int thread_id = omp_get_thread_num();
int total_threads = omp_get_num_threads();
int elem_per_thread = N / total_threads;
int base = thread_id * elem_per_thread;
int internal_sum = 0;
for (int i = base; i < (base + elem_per_thread); i++) {
internal_sum += ary[i];
}
#pragma omp critical
{
sum += internal_sum;
}
}
end = clock();
cout << sum;
elapsed_time = double(end - begin) / CLOCKS_PER_SEC;
}
顺序程序花费5e-06
(s),而并行程序花费0.001733
(s)。我正在使用g++ -std=c++11 main.cpp -fopenmp -O3 && ./a.out
答案 0 :(得分:4)
顺序程序可以优化为无所事事。这是因为唯一的副作用是sum
的值,而sum
的值在您的程序中是不可观察的。
使用OpenMP时,复杂的线程处理使编译器无法意识到您没有做任何事情。
可以避免这种情况的简单方法是添加return sum;
,现在它显示为退出代码,可以观察到,因此无法优化计算。
现在,编译器仍然可以自由地从不分配ary
,因为它可以证明所有ary[i]==i
都i
,而只用{{1 }},然后在编译时计算从ary[i]
到i
的{{1}}的总和为i
,消除整个循环并将其设为1
,然后仍然需要零时间。
答案 1 :(得分:2)
事先备注:
我相信处理“手动”划分循环的方式会适得其反(除非您想了解OpenMP的工作原理)。
这就是为什么我首先建议您对reduction
操作使用更标准的方法。您始终可以检查它是否在性能方面得到相同的结果。
另一个要点是,在没有omp_
选项的情况下,无法使用整个代码-openmp
函数进行编译。
卧推
所以我准备了以下代码:
标题
#include <iostream>
#include <fstream>
#include <omp.h>
#include <cmath>
#include <chrono>
#include <iomanip>
。 具有非常简单的添加操作的测试功能
void test_simple(long long int N, int * ary, double & sum, long long int & elapsed_milli)
{
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
start = std::chrono::system_clock::now();
double local_sum = 0.0;
#pragma omp parallel
{
#pragma omp for reduction(+:local_sum)
for (long long int i = 0; i < N; i++) {
local_sum += ary[i];
}
}
sum = local_sum;
end = std::chrono::system_clock::now();
elapsed_milli = std::chrono::duration_cast<std::chrono::microseconds>
(end-start).count();
}
。 具有复杂的CPU密集型操作符号的测试函数(x)atan(sqrt(cos(x)^ 2 + sin(0.5x)^ 2)
void test_intensive(long long int N, int * ary, double & sum, long long int & elapsed_milli)
{
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
start = std::chrono::system_clock::now();
double local_sum = 0.0;
#pragma omp parallel
{
double c, s;
#pragma omp for reduction(+:local_sum)
for (long long int i = 0; i < N; i++) {
c = cos(double(ary[i]));
s = sin(double(ary[i])*0.5);
local_sum += atan(sqrt(c*c+s*s));
}
}
sum = local_sum;
end = std::chrono::system_clock::now();
elapsed_milli = std::chrono::duration_cast<std::chrono::microseconds>
(end-start).count();
}
。主要功能
using namespace std;
int main() {
long long int N = 1073741825,i;
int * ary = new int[N];
srand (0);
for (i = 0; i < N; i++) { ary[i] = rand()-RAND_MAX/2; }
double sum = 0.0;
sum = 0.0;
long long int elapsed_milli;
cout <<"#"<<setw(19)<<"N"<<setw(20)<<"µs"<< endl;
for(i=128; i<N; i=i*2)
{
test_intensive(i, ary, sum, elapsed_milli);
//test_simple(i, ary, sum, elapsed_milli);
cout << setw(20)<<i<<setw(20)<<elapsed_milli << setw(20)<<sum<<endl;
}
}
编译(使用icpc)
顺序(无OpenMP)版本使用:
icpc test_omp.cpp -O3 --std=c++0x
OpenMP(OpenMP)版本是使用
编译的:icpc test_omp.cpp -O3 --std=c++0x -openmp
测量
时间测量是通过chrono
和high_precision_clock
完成的,并且我的机器上的极限精度为微秒,因此使用std::chrono::microseconds
(毫无意义地寻找更高的精度)
得出的结论
#pragma omp
越过)。 test_
”函数(i = 128)时仔细研究“密集型案例”,则在OpenMP案例中的时间成本要比在No OpenMP案例中的时间成本高得多。在第二次调用中(i = 256),我们没有看到使用OpenMP的好处,但是时间安排是一致的。
我们可以看到,在少量样本中我们没有观察到可伸缩性。在简单的测试案例中,这一点更加清楚。换句话说,并行部分中的操作量必须足够高,以使线程池管理所需的时间可以忽略不计。否则,将操作分为线程是没有意义的。
在这种情况下(使用我使用的处理器),最小样本数约为100000。但是,如果我使用256个线程,则肯定约为600万。
摘要
#pragma omp for
应该始终位于最外面的“可能”循环中。答案 2 :(得分:1)
如Max Langhof和user463035818所建议,该程序受内存限制。我更改了程序,以完成除累加之外的其他操作。也就是说,我将sum += ary[i]
更改为sum += (pow(ary[i], 1.1) + pow(ary[i], 1.2)) / 100000000.0
,并在并行程序中执行了相同的更改并测量了时间。并行程序的速度提高了2倍。如果该程序受IO限制,我想我不能做很多事情来使它与OpenMP一起更快。否则,请告诉我。