OpenMP:在线程之间共享数组

今天是个好日子!

我正在进行分子动力学模拟,最近我开始尝试并行实施它。 初看起来,一切看起来都很简单:在最耗时的循环之前写入#pragma omp parallel for指令。 但是,正如它发生的那样,这些循环中的函数对数组或函数进行操作,或者,对于属于我类的对象的数组,这些对象包含有关此系统上的粒子系统和函数的所有信息,因此,当我添加#编译指令在最耗时的循环之前,尽管事实上我的2核心4线程处理器已经完全加载,计算时间实际上增加了几次。

为了解决这个问题,我写了另一个更简单的程序。 该测试程序执行两个相同的循环,一个并行,另一个循环。 测量执行这两个循环所花费的时间。 结果令我感到惊讶:每当并行计算第一个循环时,与串行模式(分别为1500和6000 ms)相比,其计算时间减少,但第二个循环的计算时间急剧增加(15 000对串行6000)。

我尝试使用private()和firstprivate()子句,但结果相同。 无论如何不应该在并行区域自动共享之前定义和初始化每个变量? 如果在另一个矢量vec2上执行,则第二个循环的计算时间将恢复正常,但对于每次迭代创建新矢量显然不是选项。 我也尝试将vec1的实际更新放入#pragma omp关键区域,但这也不算什么好处。 没有帮助添加共享(vec1)条款。

如果您能指出我的错误并展示正确的方式,我将不胜感激。

是否有必要将私人(i)加入代码?

这是测试程序:

#include "stdafx.h"
#include <omp.h>
#include <array>
#include <time.h>
#include <vector>
#include <iostream>
#include <Windows.h>
using namespace std;
#define N1  1000
#define N2  4000
#define dim 1000

int main(){
    vector<int>res1,res2;
    vector<double>vec1(dim),vec2(N1);
    clock_t t, tt;
    int k=0;
    for( k = 0; k<dim; k++){
        vec1[k]=1;
    }

    t = clock();

    #pragma omp parallel 
        {
        double temp; 
        int i,j,k;
        #pragma omp for private(i)
            for( i = 0; i<N1; i++){
                for(j = 0; j<N2; j++){  
                    for( k = 0; k<dim; k++){
                        temp+= j;
                    }
                }
                vec1[i]+=temp;
                temp = 0;
            }
        }
    tt = clock();
    cout<<tt-t<<endl;
    for(int k = 0; k<dim; k++){
        vec1[k]=1;
    }
    t = clock();
                for(int g = 0; g<N1; g++){
        for(int h = 0; h<N2; h++){
            for(int y = 0; y<dim; y++){
                vec1[g]+=h; 
            }
        }
    }
    tt = clock();
    cout<<tt-t<<endl;
    getchar();
}

感谢您的时间!

PS我使用Visual Studio 2012,我的处理器是Intel Core i3-2370M。 我的程序集文件分为两部分:

http://pastebin.com/suXn35xj

http://pastebin.com/EJAVabhF


恭喜! Microsoft已经公开了另一个不好的OpenMP实现。 我最初的理论是,问题来自Sandy Bridge中分区的L3缓存和后来的Intel CPU。 但是,仅在矢量的前半部分运行第二个循环的结果并未证实该理论。 然后它必须是在启用OpenMP时触发的代码生成器中的某些内容。 装配输出证实了这一点。

基本上编译器在启用OpenMP编译时不会优化串行循环。 这是经济放缓的原因。 部分问题也是通过使第二个循环与第一个循环不相同而引入的。 在第一个循环中,您将中间值累加到临时变量中,编译器将其优化为注册变量,而在第二种情况下,您会在每次迭代中调用operator[] 。 当您在没有启用OpenMP的情况下编译时,代码优化器会将第二个循环转换为与第一个循环非常相似的东西,因此两个循环的运行时间几乎相同。

当您启用OpenMP时,代码优化器不优化第二个循环,并且运行速度较慢。 事实上,您的代码在执行并行块之前与减速无关。 我的猜测是代码优化器无法掌握vec1超出OpenMP parallel区域范围的事实,因此不应再将其视为共享变量,并且可以优化循环。 显然,这是一个“功能”,它是在Visual Studio 2012中引入的,因为即使启用了OpenMP,Visual Studio 2010中的代码生成器也能够优化第二个循环。

一种可能的解决方案是迁移到Visual Studio 2010.另一个(假设的,因为我没有VS2012)解决方案是将第二个循环提取到一个函数中,并通过引用传递该矢量。 希望编译器能够足够聪明地优化单独函数中的代码。

这是一个非常糟糕的趋势。 微软已经放弃在Visual C ++中支持OpenMP。 他们的实现仍然(几乎)只符合OpenMP 2.0(因此没有明确的任务和其他OpenMP 3.0+好处),像这样的错误并没有让事情变得更好。 我建议您切换到另一个支持OpenMP的编译器(英特尔C / C ++编译器,GCC,任何非Microsoft)或切换到其他独立于编译器的线程范例,例如英特尔线程构建模块。 微软显然正在推动他们的.NET平行库,这就是所有开发过程中的地方。


大胖子警告

不要使用clock()来测量经过的挂钟时间! 这只能在Windows上按预期运行。 在大多数Unix系统(包括Linux)中, clock()实际上会返回进程自创建以来所有线程在CPU中消耗的总时间 。 这意味着clock()可能返回的值比经过的挂钟时间大几倍(如果程序以很多繁忙的线程运行)或几倍于挂钟时间(如果程序睡眠或等待测量之间的IO事件)。 相反,在OpenMP程序中,应该使用便携式定时器函数omp_get_wtime()

链接地址: http://www.djcxy.com/p/13901.html

上一篇: OpenMP: sharing arrays between threads

下一篇: Fortran OpenMP where will the array be allocated