首页 文章

OpenMP:在线程之间共享数组

提问于
浏览
7

今天是个好日子!

我正在进行分子动力学模拟,最近我开始尝试并行实现它 . 乍一看,一切看起来都很简单:在最耗时的循环之前编写#pragma omp parallel for directive . 但实际上,这些循环中的函数在数组上运行,或者,确切地说,属于我的类的对象的数组,该数组包含有关此系统的粒子系统和函数的所有信息,因此当我添加#时pragma指令在最耗时的循环之一之前,尽管我的2核4线程处理器已满载,但计算时间实际上增加了几倍 .

为了解决这个问题,我写了另一个更简单的程序 . 该测试程序执行两个相同的循环,一个并行,第二个循环 - 串行 . 测量执行这两个循环所花费的时间 . 结果让我感到惊讶:每当第一个循环并行计算时,其计算时间与串行模式(分别为1500和6000毫秒)相比有所下降,但第二个循环的计算时间急剧增加(15000对6000连续) .

我尝试使用private()和firstprivate()子句,但结果是一样的 . 不应该自动共享并行区域之前定义和初始化的每个变量吗?如果在另一个向量上执行,第二个循环的计算时间恢复正常:vec2,但是为每次迭代创建一个新向量显然不是一个选项 . 我也尝试将vec1的实际更新放到#pragma omp关键区域,但这也没有任何好处 . 没有帮助添加Shared(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();
}

感谢您的时间!

附:我使用visual studio 2012,我的处理器是Intel Core i3-2370M . 我的汇编文件分为两部分:

http://pastebin.com/suXn35xj

http://pastebin.com/EJAVabhF

1 回答

  • 9

    恭喜!微软提供了另一个糟糕的OpenMP实现 . 我最初的理论是问题来自Sandy Bridge中的分区L3缓存和后来的Intel CPU . 但是仅在向量的前半部分运行第二个循环的结果并未证实该理论 . 然后它必须是在启用OpenMP时触发的代码生成器中的某些内容 . 装配输出确认了这一点 .

    基本上,编译器在启用OpenMP编译时不会优化串行循环 . 这就是减速的来源 . 通过使第二个循环与第一个循环不同,您自己也引入了部分问题 . 在第一个循环中,您将中间值累积到临时变量中,编译器将其优化为寄存器变量,而在第二种情况下,您在每次迭代时调用 operator[] . 在没有启用OpenMP的情况下进行编译时,代码优化器会将第二个循环转换为与第一个循环非常相似的内容,因此两个循环的运行时间几乎相同 .

    启用OpenMP时,代码优化器不会优化第二个循环,并且运行速度会慢一些 . 您的代码在此之前执行并行块与减速无关 . 我的猜测是代码优化器无法掌握 vec1 超出OpenMP parallel 区域范围的事实,因此它不应再被视为共享变量,并且可以优化循环 . 显然这是"feature",它是在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)或切换到某些其他编译器独立的线程范例,例如Intel Threading Building Blocks . 微软显然正在推动他们的.NET并行库,而这正是所有开发的发展方向 .


    大胖警告

    Do not use clock() to measure the elapsed wall-clock time! 这仅在Windows上按预期工作 . 在大多数Unix系统(包括Linux)上, clock() 实际上返回 the total consumed CPU time by all threads in the process since it was created . 这意味着 clock() 可以返回比经过的挂钟时间大几倍的值(如果程序运行许多忙线程)或者比挂钟时间短几倍(如果程序在IO事件上休眠或等待)测量之间) . 相反,在OpenMP程序中,应使用便携式计时器功能 omp_get_wtime() .

相关问题