Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/57.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C OpenMP尴尬的并行for循环,没有加速_C_Loops_Parallel Processing_Openmp - Fatal编程技术网

C OpenMP尴尬的并行for循环,没有加速

C OpenMP尴尬的并行for循环,没有加速,c,loops,parallel-processing,openmp,C,Loops,Parallel Processing,Openmp,我有一个非常简单的并行for循环,它只是将零写入一个整数数组。但事实证明,线程越多,循环越慢。我认为这是由于一些缓存抖动造成的,所以我对时间表、块大小、\uuuuu restrict\uuuu、将并行块嵌套在并行块内以及刷新进行了研究。然后我注意到读取一个数组来进行缩减也是比较慢的 这显然是非常简单的,并且应该几乎线性地加速。我错过了什么 完整代码: #include <omp.h> #include <vector> #include <iostream>

我有一个非常简单的并行
for
循环,它只是将零写入一个整数数组。但事实证明,线程越多,循环越慢。我认为这是由于一些缓存抖动造成的,所以我对时间表、块大小、
\uuuuu restrict\uuuu
、将并行块嵌套在并行块内以及刷新进行了研究。然后我注意到读取一个数组来进行缩减也是比较慢的

这显然是非常简单的,并且应该几乎线性地加速。我错过了什么

完整代码:

#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;
}
如果我使用
-O2
运行,g++能够优化串行缩减,并且我得到零时间,因此
-O1
。另外,放入
omp\u set\u num\u线程(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,具有超线程功能,但在一位同事的i5上也观察到了相同的行为,它有4个内核,没有超线程功能。我们都在运行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;
}
但仍然没有加速,只是不再慢了

EDIT2

正如user8046所建议的,代码内存严重受限。正如Z玻色子所建议的那样,串行代码很容易被优化掉,并且不确定这里测量的是什么。所以我做了一个小小的改变,把和放在循环之外,这样它就不会在
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,这是有意义的,因为超线程内核共享内存通道)。

您使用的是std::clock,它报告使用的是CPU时间,而不是实时时间。因此,每个处理器的时间加起来总是高于单线程时间(由于开销)


您发现了定时错误。仍然没有加速,因为您的两个测试用例都有严重的内存限制。在典型的消费类硬件上,所有内核都共享一条内存总线,因此使用更多线程并不能提供更多带宽,而且,由于这是瓶颈,因此会导致加速。如果您减少问题大小,使其适合缓存,或者如果您增加每个数据的计算数量,例如,如果您正在计算exp(vec[i])或1/vec[i]的减少量,则这可能会发生变化。对于memset:你可以用一个线程使内存饱和,你永远不会看到那里的加速。(仅当您可以访问具有更多线程的第二条内存总线时,如某些多套接字体系结构)。
关于减少的一点意见是,这很可能不是用锁实现的,这将是非常低效的,但使用的加法树的对数加速效果并不太差。

除了在Linux中使用时钟函数的错误之外,您的其余问题可以通过阅读这些问题/答案来回答

因此,您应该看到使用memset的多线程以及即使在单个socket系统上进行缩减也会带来显著的好处。我已经编写了自己的工具来测量带宽。您可以从我的i5-4250U(Haswell)中找到一些结果,其中2个内核(GCC 4.8、Linux 3.13、EGLIBC 2.19)运行超过1GB
vsum
是您的缩减。请注意,即使在这两个核心系统上也有显著的改进

一根线

C standard library
                       GB    time(s)       GB/s     GFLOPS   efficiency
memset:              0.50       0.80       6.68       0.00        inf %
memcpy:              1.00       1.35       7.93       0.00        inf %

Agner Fog's asmlib
                       GB    time(s)       GB/s     GFLOPS   efficiency
memset:              0.50       0.71       7.53       0.00        inf %
memcpy:              1.00       0.93      11.51       0.00        inf %

my_memset   
                     0.50       0.71       7.53       0.00        inf %


FMA3 reduction tests
                       GB    time(s)       GB/s     GFLOPS   efficiency
vsum:                0.50       0.53      10.08       2.52        inf %
vmul:                0.50       0.68       7.93       1.98        inf %
vtriad:              0.50       0.70       7.71       3.85        inf %
dot                  1.00       1.08       9.93       2.48        inf %
两条线

C standard library
                       GB    time(s)       GB/s     GFLOPS   efficiency
memset:              0.50       0.64       8.33       0.00        inf %
memcpy:              1.00       1.10       9.76       0.00        inf %

Agner Fog's asmlib
                       GB    time(s)       GB/s     GFLOPS   efficiency
memset:              0.50       0.36      14.98       0.00        inf %
memcpy:              1.00       0.66      16.30       0.00        inf %

my_memset
                     0.50       0.36      15.03       0.00        inf %


FMA3 tests
standard sum tests with OpenMP: 2 threads
                       GB    time(s)       GB/s     GFLOPS   efficiency
vsum:                0.50       0.41      13.03       3.26        inf %
vmul:                0.50       0.39      13.67       3.42        inf %
vtriad:              0.50       0.44      12.20       6.10        inf %
dot                  1.00       0.97      11.11       2.78        inf %
这是我的自定义memset函数(我还有其他几个类似的测试)

编辑:

我测试了你的代码并做了一些修改。我通过使用多个线程进行缩减和memset获得了更快的时间

max threads: 4
serial reduction
dtime 1.86, sum 705032704
parallel reduction
dtime 1.39 s, sum 705032704
serial memset
dtime 2.95 s
parallel memset
dtime 2.44 s
serial my_memset
dtime 2.66 s
parallel my_memset
dtime 1.35 s
下面是我使用的代码(g++foo.cpp-fopenmp-O3-ffast-math)

#包括
#包括
#包括
#包括
#包括
#包括
作废我的内存集(int*s,int c,size\n){
int i;
__m128i v=_mm_set1_epi32(c);

对于(i=0;i如何进行令人尴尬的平行化?sum变量上(必然)有一个锁。@MadScienceDreams你是对的,当我写问题标题时,我只是在试验(令人尴尬的平行化)写入标题所指的数组。我后来做了缩减实验。但仍然可以使用专用累加器(处理数千个元素)按线程对循环执行double缩减,然后按线程对部分和进行树状或串行缩减(共有12个)。这几乎是令人尴尬的并行(不确定编译器是否会以这种方式实现它,不过-我知道我可以)。@HighPerformanceMark True,很好地发现。我只使用了
clock()
而不是一些更精确的函数使其更简单…失败。我在
g++
ver
4.8.2-19ubuntu1
和Xeon E5540上得到了与您不同的结果。“并行缩减”的速度是“串行缩减”的2倍@因此,它的速度稍微快一些。
max threads:16串行减少已用时钟节拍:3.81508并行减少已用时钟节拍:1.91143串行内存集已用时钟节拍:4.37205并行内存集已用时钟节拍:2.95767
一个有趣的想法……因此,我没有写
sum+=vec[i]
而是写
sum+=sin(exp(sqrt(double(vec[i]))
和类似地,代替
vec[i]=0
i放置
sin(exp(sqrt(double(i)))
,类型转换确保“正常”使用的是不同版本的数学函数,而不是整数函数。这会将时间更改如下:
串行缩减:17.9秒,并行缩减:99.4秒,串行内存集:161.4秒,并行内存集:38.6秒
。所以我想你是对的,至少
C standard library
                       GB    time(s)       GB/s     GFLOPS   efficiency
memset:              0.50       0.64       8.33       0.00        inf %
memcpy:              1.00       1.10       9.76       0.00        inf %

Agner Fog's asmlib
                       GB    time(s)       GB/s     GFLOPS   efficiency
memset:              0.50       0.36      14.98       0.00        inf %
memcpy:              1.00       0.66      16.30       0.00        inf %

my_memset
                     0.50       0.36      15.03       0.00        inf %


FMA3 tests
standard sum tests with OpenMP: 2 threads
                       GB    time(s)       GB/s     GFLOPS   efficiency
vsum:                0.50       0.41      13.03       3.26        inf %
vmul:                0.50       0.39      13.67       3.42        inf %
vtriad:              0.50       0.44      12.20       6.10        inf %
dot                  1.00       0.97      11.11       2.78        inf %
void my_memset(int *s, int c, size_t n) {
    int i;
    __m128i v = _mm_set1_epi32(c);
    #pragma omp parallel for
    for(i=0; i<n/4; i++) {
        _mm_stream_si128((__m128i*)&s[4*i], v);
    }
}
double sum = 0;
tic();
for(int c = 0; c < COUNT; ++ c) { 
    #pragma omp parallel for reduction(+:sum)
    for(int i = 0; i < sz_i; ++ i)
        sum += ptr[i];
}
toc();
printf("sum %f\n", sum);
max threads: 4
serial reduction
dtime 1.86, sum 705032704
parallel reduction
dtime 1.39 s, sum 705032704
serial memset
dtime 2.95 s
parallel memset
dtime 2.44 s
serial my_memset
dtime 2.66 s
parallel my_memset
dtime 1.35 s
#include <omp.h>
#include <vector>
#include <iostream>
#include <ctime>
#include <stdio.h>

#include <xmmintrin.h>

void my_memset(int *s, int c, size_t n) {
    int i;
    __m128i v = _mm_set1_epi32(c);
    for(i=0; i<n/4; i++) {
        _mm_stream_si128((__m128i*)&s[4*i], v);
    }
}

void my_memset_omp(int *s, int c, size_t n) {
    int i;
    __m128i v = _mm_set1_epi32(c);
    #pragma omp parallel for
    for(i=0; i<n/4; i++) {
        _mm_stream_si128((__m128i*)&s[4*i], v);
    }
}

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;
    double dtime;
    int sum;

    dtime = -omp_get_wtime();
    sum = 0;
    for(int c = 0; c < COUNT; ++ c) {
        for(size_t i = 0; i < sz; ++ i)
            sum += vec[i];
    }
    dtime += omp_get_wtime();
    printf("dtime %.2f, sum %d\n", dtime, sum);

    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;


    dtime = -omp_get_wtime();
    sum = 0;
    for(int c = 0; c < COUNT; ++ c) {
        #pragma omp parallel for default(none) reduction(+:sum)
        for(int i = 0; i < sz_i; ++ i)
            sum += ptr[i];
    }
    dtime += omp_get_wtime();
    printf("dtime %.2f s, sum %d\n", dtime, sum);

    std::cout << "serial memset" << std::endl;

    dtime = -omp_get_wtime();
    for(int c = 0; c < COUNT; ++ c) {
        for(size_t i = 0; i < sz; ++ i)
            vec[i] = 0;
    }   
    dtime += omp_get_wtime();
    printf("dtime %.2f s\n", dtime);

    std::cout << "parallel memset" << std::endl;
    dtime = -omp_get_wtime();
    for(int c = 0; c < COUNT; ++ c) {
        #pragma omp parallel for default(none)
        for(int i = 0; i < sz_i; ++ i)
            ptr[i] = 0;
    }
    dtime += omp_get_wtime();
    printf("dtime %.2f s\n", dtime);

    std::cout << "serial my_memset" << std::endl;

    dtime = -omp_get_wtime();
    for(int c = 0; c < COUNT; ++ c) my_memset(ptr, 0, sz_i);

    dtime += omp_get_wtime();
    printf("dtime %.2f s\n", dtime);

    std::cout << "parallel my_memset" << std::endl;
    dtime = -omp_get_wtime();
    for(int c = 0; c < COUNT; ++ c) my_memset_omp(ptr, 0, sz_i);
    dtime += omp_get_wtime();
    printf("dtime %.2f s\n", dtime);

    return 0;
}