为什么 OpenMP 不加速基本循环?
Why a basic loop isn't accelerated by OpenMP?
我有一个基本循环:
int i, n=50000000;
for (i=0 ; i<n ; i++)
{
register float val = rand()/(float)RAND_MAX;
}
我想用 OpenMP 加速。我之前设置:
omp_set_dynamic(0);
omp_set_num_threads(nths);
和nths=4
最后的循环是:
int i, n=50000000;
#pragma omp parallel for firstprivate(n) default(shared)
for (i=0 ; i<n ; i++)
{
register float val = rand()/(float)RAND_MAX;
}
非并行循环需要 1.12 秒来执行,并行循环需要 21.04 秒(根据我的 linux 优先级进程,它可能会有很大差异)。我在 x86 平台上使用 Ubuntu 和 4 个 CPU,每个 1 个线程。我用 g++
(我需要它)编译,我用 -fopenmp
标记,我使用库 -lgomp
为什么 OpenMP 不加速这个基本循环?
编辑:
关于答案,我将循环内部更改为:
for (i=0 ; i<n ; i++)
{
a[i]=i;
b[i]=i;
c[i]=a[i]+b[i];
}
with n=500000
和 pragma:
#pragma omp parallel for firstprivate(n) default(shared) schedule(dynamic) num_threads(4)
我也将代码更改为仅使用 gcc
,我遇到了同样的问题:
With 1 Thread
Test ms = 0.003000
Test Omp ms = 19.695000
With 4 Threads
Test ms = 0.003000
Test Omp ms = 240.990000
编辑2:
我改变了使用 OpenMP 时测量时间的方式。我使用 omp_get_wtime()
函数代替 clock()
函数,结果更好。
rand()
根本不是线程安全函数。里面有一个 PRNG,它有一个状态,因此在没有同步的情况下不能被多个线程调用。使用不同的 PRNG(C++11 有很多,Boost 也是),每个线程使用一个生成器并且不要忘记用 不同的种子值 为它们播种(提防 time(NULL)
).
更新
由于线程创建的开销,n
为 500k 可能太小而无法获得任何加速。你能测试一下,例如,n
设置为 500M 吗?此外,没有 OpenMP 的时间低得令人怀疑(可能不会这么低 n
)。循环后的 a
、b
和 c
数组在做什么?如果没有,编译器可以优化顺序代码中的整个循环。 对这些数组做一些事情,例如,打印出它们的总和(在测量部分之外)。
我运行你的代码很快就通过了我的系统。
首先,在数组加法的情况下,您的 50M 勉强足以显示胜利,但它确实如此 - 如果 OpenMP 设置正确。
在你的情况下,schedule(dynamic)
正在杀死你 - 它告诉编译器在 运行时 将工作分配给团队。如果您不能预先确定您的工作量,那将是有意义的 - 但在这种情况下,它是完全可以预测的,因为每次迭代的工作量完全相同。
在超线程 CPU 上编辑您的示例(见下文)和 运行 后,我得到以下结果,所有内核都固定在最低频率。我使用 gcc 4.9.3 编译:
time ./testseq && time ./testpar
real 0m0.576s
user 0m0.504s
sys 0m0.072s
real 0m0.285s
user 0m0.968s
sys 0m0.123s
如您所见,real
值(即 "wallclock time")大致减半。 user
时间增加,因为线程启动和关闭。
如果我添加 schedule(dynamic)
子句,并行化结果会发生很大变化:
real 0m4.181s
user 0m14.886s
sys 0m1.283s
所有额外的工作负载都花在了完成少量工作并寻找下一批的线程上。这需要锁定 - 这会杀死你的第二个例子。请仅在遇到负载平衡问题时使用 schedule(dynamic)
- 每个迭代器的工作量变化很大。
为了完整披露,我 运行 使用以下完整源代码:
CXXFLAGS=-std=c++11 -I. -Wall -Wextra -g -pthread
all: testseq testpar
testpar: test.cpp
${CXX} -o $@ $^ -fopenmp ${CXXFLAGS}
testseq: test.cpp
${CXX} -o $@ $^ ${CXXFLAGS}
clean:
rm -f *.o *~ test
和test.cpp
:
#include <omp.h>
constexpr int n=50*1000*1000;
float a[n];
float b[n];
float c[n];
int main(void) {
#pragma omp parallel for schedule(dynamic)
for (int i=0 ; i<n ; i++) {
a[i]=i;
b[i]=i;
c[i]=a[i]+b[i];
}
}
请注意,我还删除了您 parallel for
的其他条款 - 您需要 none 个。
我有一个基本循环:
int i, n=50000000;
for (i=0 ; i<n ; i++)
{
register float val = rand()/(float)RAND_MAX;
}
我想用 OpenMP 加速。我之前设置:
omp_set_dynamic(0);
omp_set_num_threads(nths);
和nths=4
最后的循环是:
int i, n=50000000;
#pragma omp parallel for firstprivate(n) default(shared)
for (i=0 ; i<n ; i++)
{
register float val = rand()/(float)RAND_MAX;
}
非并行循环需要 1.12 秒来执行,并行循环需要 21.04 秒(根据我的 linux 优先级进程,它可能会有很大差异)。我在 x86 平台上使用 Ubuntu 和 4 个 CPU,每个 1 个线程。我用 g++
(我需要它)编译,我用 -fopenmp
标记,我使用库 -lgomp
为什么 OpenMP 不加速这个基本循环?
编辑:
关于答案,我将循环内部更改为:
for (i=0 ; i<n ; i++)
{
a[i]=i;
b[i]=i;
c[i]=a[i]+b[i];
}
with n=500000
和 pragma:
#pragma omp parallel for firstprivate(n) default(shared) schedule(dynamic) num_threads(4)
我也将代码更改为仅使用 gcc
,我遇到了同样的问题:
With 1 Thread
Test ms = 0.003000
Test Omp ms = 19.695000
With 4 Threads
Test ms = 0.003000
Test Omp ms = 240.990000
编辑2:
我改变了使用 OpenMP 时测量时间的方式。我使用 omp_get_wtime()
函数代替 clock()
函数,结果更好。
rand()
根本不是线程安全函数。里面有一个 PRNG,它有一个状态,因此在没有同步的情况下不能被多个线程调用。使用不同的 PRNG(C++11 有很多,Boost 也是),每个线程使用一个生成器并且不要忘记用 不同的种子值 为它们播种(提防 time(NULL)
).
更新
由于线程创建的开销,n
为 500k 可能太小而无法获得任何加速。你能测试一下,例如,n
设置为 500M 吗?此外,没有 OpenMP 的时间低得令人怀疑(可能不会这么低 n
)。循环后的 a
、b
和 c
数组在做什么?如果没有,编译器可以优化顺序代码中的整个循环。 对这些数组做一些事情,例如,打印出它们的总和(在测量部分之外)。
我运行你的代码很快就通过了我的系统。 首先,在数组加法的情况下,您的 50M 勉强足以显示胜利,但它确实如此 - 如果 OpenMP 设置正确。
在你的情况下,schedule(dynamic)
正在杀死你 - 它告诉编译器在 运行时 将工作分配给团队。如果您不能预先确定您的工作量,那将是有意义的 - 但在这种情况下,它是完全可以预测的,因为每次迭代的工作量完全相同。
在超线程 CPU 上编辑您的示例(见下文)和 运行 后,我得到以下结果,所有内核都固定在最低频率。我使用 gcc 4.9.3 编译:
time ./testseq && time ./testpar
real 0m0.576s
user 0m0.504s
sys 0m0.072s
real 0m0.285s
user 0m0.968s
sys 0m0.123s
如您所见,real
值(即 "wallclock time")大致减半。 user
时间增加,因为线程启动和关闭。
如果我添加 schedule(dynamic)
子句,并行化结果会发生很大变化:
real 0m4.181s
user 0m14.886s
sys 0m1.283s
所有额外的工作负载都花在了完成少量工作并寻找下一批的线程上。这需要锁定 - 这会杀死你的第二个例子。请仅在遇到负载平衡问题时使用 schedule(dynamic)
- 每个迭代器的工作量变化很大。
为了完整披露,我 运行 使用以下完整源代码:
CXXFLAGS=-std=c++11 -I. -Wall -Wextra -g -pthread
all: testseq testpar
testpar: test.cpp
${CXX} -o $@ $^ -fopenmp ${CXXFLAGS}
testseq: test.cpp
${CXX} -o $@ $^ ${CXXFLAGS}
clean:
rm -f *.o *~ test
和test.cpp
:
#include <omp.h>
constexpr int n=50*1000*1000;
float a[n];
float b[n];
float c[n];
int main(void) {
#pragma omp parallel for schedule(dynamic)
for (int i=0 ; i<n ; i++) {
a[i]=i;
b[i]=i;
c[i]=a[i]+b[i];
}
}
请注意,我还删除了您 parallel for
的其他条款 - 您需要 none 个。