【发布时间】:2014-09-02 13:33:34
【问题描述】:
我有一个看似非常简单的并行for 循环,它只是将零写入整数数组。但事实证明,线程越多,循环越慢。我认为这是由于一些缓存抖动造成的,所以我使用了时间表、块大小、__restrict__,将并行嵌套在并行块内,然后刷新。然后我注意到读取数组进行归约也比较慢。
这显然应该非常简单,并且应该几乎线性加速。我在这里错过了什么?
完整代码:
#include <omp.h>
#include <vector>
#include <iostream>
#include <ctime>
void tic(), toc();
int main(int argc, const char *argv[])
{
const int COUNT = 100;
const size_t sz = 250000 * 200;
std::vector<int> vec(sz, 1);
std::cout << "max threads: " << omp_get_max_threads()<< std::endl;
std::cout << "serial reduction" << std::endl;
tic();
for(int c = 0; c < COUNT; ++ c) {
double sum = 0;
for(size_t i = 0; i < sz; ++ i)
sum += vec[i];
}
toc();
int *const ptr = vec.data();
const int sz_i = int(sz); // some OpenMP implementations only allow parallel for with int
std::cout << "parallel reduction" << std::endl;
tic();
for(int c = 0; c < COUNT; ++ c) {
double sum = 0;
#pragma omp parallel for default(none) reduction(+:sum)
for(int i = 0; i < sz_i; ++ i)
sum += ptr[i];
}
toc();
std::cout << "serial memset" << std::endl;
tic();
for(int c = 0; c < COUNT; ++ c) {
for(size_t i = 0; i < sz; ++ i)
vec[i] = 0;
}
toc();
std::cout << "parallel memset" << std::endl;
tic();
for(int c = 0; c < COUNT; ++ c) {
#pragma omp parallel for default(none)
for(int i = 0; i < sz_i; ++ i)
ptr[i] = 0;
}
toc();
return 0;
}
static clock_t ClockCounter;
void tic()
{
ClockCounter = std::clock();
}
void toc()
{
ClockCounter = std::clock() - ClockCounter;
std::cout << "\telapsed clock ticks: " << ClockCounter << std::endl;
}
运行这个会产生:
g++ omp_test.cpp -o omp_test --ansi -pedantic -fopenmp -O1
./omp_test
max threads: 12
serial reduction
elapsed clock ticks: 1790000
parallel reduction
elapsed clock ticks: 19690000
serial memset
elapsed clock ticks: 3860000
parallel memset
elapsed clock ticks: 20800000
如果我使用-O2 运行,g++ 能够优化串行减少并且我得到零时间,因此-O1。此外,加上omp_set_num_threads(1); 会使时间更加相似,尽管仍然存在一些差异:
g++ omp_test.cpp -o omp_test --ansi -pedantic -fopenmp -O1
./omp_test
max threads: 1
serial reduction
elapsed clock ticks: 1770000
parallel reduction
elapsed clock ticks: 7370000
serial memset
elapsed clock ticks: 2290000
parallel memset
elapsed clock ticks: 3550000
这应该很明显,我觉得我没有看到非常基本的东西。我的 CPU 是 Intel(R) Xeon(R) CPU E5-2640 0 @ 2.50GHz,具有超线程,但在同事的具有 4 核且没有超线程的 i5 上观察到相同的行为。我们都在运行 Linux。
编辑
似乎一个错误是在时间方面,运行:
static double ClockCounter;
void tic()
{
ClockCounter = omp_get_wtime();//std::clock();
}
void toc()
{
ClockCounter = omp_get_wtime()/*std::clock()*/ - ClockCounter;
std::cout << "\telapsed clock ticks: " << ClockCounter << std::endl;
}
产生更“合理”的时间:
g++ omp_test.cpp -o omp_test --ansi -pedantic -fopenmp -O1
./omp_test
max threads: 12
serial reduction
elapsed clock ticks: 1.80974
parallel reduction
elapsed clock ticks: 2.07367
serial memset
elapsed clock ticks: 2.37713
parallel memset
elapsed clock ticks: 2.23609
但仍然没有加速,只是不再慢了。
EDIT2:
正如 user8046 所建议的那样,该代码受大量内存限制。正如 Z boson 所建议的那样,串行代码很容易优化掉,并且不确定这里测量的是什么。所以我做了一个小改动,将总和放在循环之外,这样它就不会在c 的每次迭代中归零。我还用sum+=F(vec[i]) 替换了归约操作,用vec[i] = F(i) 替换了memset 操作。运行方式:
g++ omp_test.cpp -o omp_test --ansi -pedantic -fopenmp -O1 -D"F(x)=sqrt(double(x))"
./omp_test
max threads: 12
serial reduction
elapsed clock ticks: 23.9106
parallel reduction
elapsed clock ticks: 3.35519
serial memset
elapsed clock ticks: 43.7344
parallel memset
elapsed clock ticks: 6.50351
计算平方根会增加线程的工作量,最终会有一些合理的加速(大约是7x,这是有道理的,因为超线程内核共享内存通道)。
【问题讨论】:
-
减少如何令人尴尬地并行? sum 变量有一个(必要的)锁。
-
@MadScienceDreams 你说得对,当我写问题标题时,我只是在尝试(令人尴尬的并行)写入标题所指的数组。还原实验后来发生在我身上。但是,double 的减少仍然可以通过使用私有累加器(处理数千个元素)的每个线程 for 循环来实现,然后对每个线程的部分和进行树状或串行减少(总共有 12 个) )。这几乎是令人尴尬的并行(虽然不确定编译器是否会以这种方式实现它 - 我知道我可以)。
-
@HighPerformanceMark 是的,很好。我只使用了
clock(),而不是一些更精确的函数来使它更简单......失败。 -
我在
g++和4.8.2-19ubuntu1和 Xeon E5540 上得到了不同的结果。 “并行缩减”是“串行缩减”的 2 倍 -
@theswine,它稍微快一点。
max threads: 16 serial reduction elapsed clock ticks: 3.81508 parallel reduction elapsed clock ticks: 1.91143 serial memset elapsed clock ticks: 4.37205 parallel memset elapsed clock ticks: 2.95767
标签: c loops parallel-processing openmp