为什么 OpenMP 不加速基本循环?

Why a basic loop isn't accelerated by OpenMP?

本文关键字:循环 加速 OpenMP 为什么      更新时间:2023-10-16

我有一个基本循环:

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,每个CPU有1个线程。我使用-fopenmp标记的g++(我需要它)进行编译,并使用库-lgomp

为什么OpenMP不加速这个基本循环?

编辑:

关于答案,我将循环的内部更改为:

for (i=0 ; i<n ; i++)
{
    a[i]=i;
    b[i]=i;
    c[i]=a[i]+b[i];
}

带有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时,我改变了测量时间的方式。与clock()函数不同,我使用了omp_get_wtime()函数,结果要好得多。

我在系统中快速运行了您的代码。首先,在阵列添加的情况下,您的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时间",大约是一半。由于线程启动和关闭,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的其他条款——您不需要它们。

rand()根本不是线程安全函数。内部有一个PRNG具有状态,因此在没有同步的情况下无法由多个线程调用。使用不同的PRNG(C++11有很多,Boost也是),每个线程使用一个生成器,不要忘记用不同的种子值对它们进行种子设定(小心time(NULL))。

更新

由于线程创建的开销,500k的CCD_ 18可能太小而无法获得任何加速。你能用n设置为500M来测试吗?此外,没有OpenMP的时间低得令人怀疑(对于如此低的n可能不是这样)。循环后,您将如何处理abc阵列?如果没有,编译器可以优化顺序代码中的整个循环对这些数组执行一些操作,例如,打印出它们的总和(在测量部分之外)。