C++ Coder

HCP高性能计算架构,实现,编译器指令优化,算法优化, LLVM CLANG OpenCL CUDA OpenACC C++AMP OpenMP MPI

C++博客 首页 新随笔 联系 聚合 管理
  98 Posts :: 0 Stories :: 0 Comments :: 0 Trackbacks

#

最近在实现一个向量相称的CUDA程序的时候,碰见一个让我很头疼的问题。基本症状是:计算结果在小数据量的时候是完全正确的,如果向量的维数增大到一定数值后,计算结果就不对了。我查了好久,终于找到了问题的源头。竟然是数据类型的问题,写下来提醒后来人!

我在内核函数中使用以下语句:

01.unsigned char tx = threadIdx.x;  
02.unsigned char bx = blockIdx.x;  
03.unsigned int id = tx + bx*blockDim.x;  

 

id竟然不对了。我需要ls个线程,如果ls的数目足够大,使得bx的值溢出了。


解决方法是: 

 

unsigned short bx = blockIdx.x;

头疼的解决总会是好的。欢迎大家的驻足,我继续。。


http://blog.csdn.net/bendanban/article/details/7971447
posted @ 2012-10-21 12:56 jackdong 阅读(471) | 评论 (0)编辑 收藏

问题描述:有很多朋友希望自己的MFC程序中能使用CUDA。就一直搜索MFC项目配置CUDA的资料。在这里以个人的经验和理解向还在迷茫的朋友们说几点,看完这几点说明,相信大家不会再迷茫。

1、首先微软提供过的各种项目模版只是简化了我们的开发时间,它的实质还是C或者C++语言。所以我们只要把神马MFC项目理解为普通的C\C++项目就可以了。

2、基于第一点,我们应该做些什么那?只要在我们的MFC项目下按照一般C\C++程序添加CUDA代码就可以了。这一点我们应该思考,添加了代码就可以了吗?当然不可以!应该思考一下文件的编译过程,在没有指定编译器的情况下你的CUDA程序怎么可能被正确编译那?你必须选择.cu文件的编译规则!如果你使用runtime,你要添加自定义编译规则,然后修改你的cu文件的属性,告诉VS你的cu问价是谁来编译的。

3、根据nvcc的编译规则,cu文件首先会被编译,然后生成一大堆的obj文件,然后是微软的编译器开始工作,他编译了剩下的程序。然后是连接器,它连接了所有obj,lib。然后你如果想执行,执行就是了。

4、如果大家觉得这种方法不可靠,完全可以把cuda的程序写成DLL的,然后在大家想使用CUDA的程序中添加lib、dll。

 

以上是我个人理解,如有可以探讨的问题,可以评论,我会及时做出回答。微笑


http://blog.csdn.net/bendanban/article/details/7606116
posted @ 2012-10-21 12:54 jackdong 阅读(403) | 评论 (0)编辑 收藏

http://blog.csdn.net/bendanban/article/details/7918391

本文以CUDA4.2为例讲解如何在Ubuntu12.04上安装CUDA

注意一点,在安装之前,必须确保自己的GPU是NVIDIA的GPU,并且支持CUDA。如果不确定自己的显卡是否支持CUDA,可以在http://developer.nvidia.com/cuda-gpus中找到支持CUDA的GPU列表。如果你的显卡是ATI的,可以使用OpenCL来获得GPU的计算资源(http://www.khronos.org/opencl).

 

如果你的GPU满足上面的要求,下面我们就开始安装了。

第一步:下载安装文件,安装文件driver,toolkit,SDK。可以在http://developer.nvidia.com/cuda/cuda-toolkit-archive下载到自己想安装的版本。特别注意一下自己的系统是多少位的,在终端执行下面的命令可以知道你的系统的位数。

[]$uname -m

 

i686是32位系统,x86_64是64位系统。至于选择那个toolkit选择Ubuntu11.04是可行的,至少我使用了几个月了没遇到问题。

 

 

第二步:安装驱动。

确保所有需要的东西都已经安装好了。

 

$sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev binutils-gold

为了不让系统打扰我们安装驱动,把一些需要的模块列入黑名单:

[]$gksu gedit /etc/modprobe.d/blacklist.conf

在打开的文件里添加一下几行

blacklist amd76x_edac
blacklist vga16fb
blacklist nouveau
blacklist rivafb
blacklist nvidiafb
blacklist rivatv

将文件保存后退出。

 

 

为了能去除所有NVIDIA的残余物,在终端中执行下面的命令:

sudo apt-get remove --purge nvidia*

这个命令可能需要执行一段时间,所以要耐心的等等。等它完成了,重新启动你的机器。在登录界面出现后,先不要登录,在键盘上按下Ctrl+Alt+F1组合键,以文本的方式登录,找到你的驱动安装文件,执行下面的命令:

sudo service lightdm stop
chmod a
+x devdriver*.run

这里devdriver*.run指的是你的驱动的名字。下面是安装。

sudo ./devdriver*.run

安装结束后,重新启动系统。

 

 

第三步:登录后,打开终端,安装toolkit

在终端下进入toolkit的目录,执行下面的命令:

chmod a+x cudatoolkit*.run
sudo .
/cudatoolkit*.run

 

cudatoolkit*.run代表你的toolkit的安装文件。

这样安装之后还要确保你安装的动态链接库可以被自己的程序找到。最好在使用CUDA之前执行下面的命令。

对于32位系统:

 

export LD_LIBRARY_PATH=/usr/local/cuda/lib:$LD_LIBRARY_PATH

对于64位系统:

export LD_LIVRARY_PATH=/usr/local/cuda/lib:$LD_LIBRARY_PATH
export LD_LIVRARY_PATH
=/usr/local/cuda/lib64:$LD_LIBRARY_PATH

第四步:安装sdk。实际上,这一步并不是使用GPU计算的必要部分,这里面只是包含了一些有用的例子。不过还是推荐大家安装这个SDK。

在终端下进入包含SDK的目录,执行下面的命令

chmod a+x cudasdk.run
.
/cudasdk.run

 

cudasdk.run代表了你的SDK的安装文件。

 

OK了,如果有问题,一定要留言奥。。。

 

PS:本文参考了一篇英文的文章,不过我没有找到那篇文章,要是有网友找到了,可以给我留言。我加上对它的引用。







 



posted @ 2012-10-21 12:51 jackdong 阅读(445) | 评论 (0)编辑 收藏

http://blog.csdn.net/bendanban/article/details/7673607
给程序计时对于程序员来说实在是太重要了,在windows上的那个clock()实在是不够精确,精度只有10ms,真让人难过。研究了下windows下使用C、C++计时的函数,给大家分享下。

主要就是两个函数的使用。我先把一段可以运行的代码贴出来,然后讲讲这两个函数。

#include <windows.h>
#include 
<stdio.h>
int main(int argc, char **argv)
{
    LARGE_INTEGER freq;
    LARGE_INTEGER start_t, stop_t;
    
double exe_time;
    QueryPerformanceFrequency(
&freq);
    fprintf(stdout, 
"The frequency of your pc is %d.\n", freq.QuadPart);
    QueryPerformanceCounter(
&start_t);
    Sleep(
1000);
    QueryPerformanceCounter(
&stop_t);
    exe_time 
= 1e3*(stop_t.QuadPart-start_t.QuadPart)/freq.QuadPart;
    fprintf(stdout, 
"Your program executed time is %fms.\n", exe_time); 
    getchar();
    
return 0;
}

 

1、LARGE_INTEGER在微软的编译器中实际上是一个union,它的定义如下:
typedef union _LARGE_INTEGER
{  
    
struct
 
    
{    
        DWORD LowPart;   
        LONG HighPart;  
    }

    
struct
 
    
{   
        DWORD LowPart;    
        LONG HighPart;  
    }
 u;  
    LONGLONG QuadPart;
}
 LARGE_INTEGER,  *PLARGE_INTEGER;


如果你使用的编译器支持64位整数,那么可以使用QuadPart来引用变量的值。如果你的编译器不支持64位整数,那么可以使用LowPart和HighPart来引用64位整数的低32位和高32位。

2、QueryPerformanceFrequncy(LARGE_INTEGER *freq)

它用于获得你的机器一秒钟执行多少次,就是你的时钟周期。

3、QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount)

它获得的是CPU从开机以来执行的时钟周期数。

 

O啦。。。。好好用用它吧。






posted @ 2012-10-21 12:45 jackdong 阅读(495) | 评论 (0)编辑 收藏

http://blog.csdn.net/bendanban/article/details/7669624

总有些童鞋想知道怎么在CUDA中使用二维数组([M][N]这种类型),其实这个很简单,不过你要完全弄明白,必须对指针,地址等概念非常清楚才行。写这篇博客解决下大家这个问题:

1、首先讲述一下在一般C语言中如何使用二维数组。

int r, c;
int **arr = (int**)malloc(ROWS*sizeof(int*));
int *data = (int*)malloc(COLS*ROWS*sizeof(int));
for (r = 0; r < ROWS; r++)
{
    arr[r] 
= data + r*COLS;
}


free(arr);
free(data);

 


 代码中的arr实个二维数组变量了,你可以在for循环之后arr[i][j]的方式使用它。

 

2、告诉你如何在CUDA中使用二维数组可以类比1中的方法,不过你要清楚几点,这几点在代码之后说明。

#include <stdio.h>
#include 
<stdlib.h>
#include 
<cuda_runtime.h>

#define ROWS 32
#define COLS 16
#define CHECK(res) if(res!=cudaSuccess){exit(-1);}
__global__ 
void Kerneltest(int **da, unsigned int rows, unsigned int cols)
{
    unsigned 
int row = blockDim.y*blockIdx.y + threadIdx.y;
    unsigned 
int col = blockDim.x*blockIdx.x + threadIdx.x;
    
if (row < rows && col < cols)
    
{
        da[row][col] 
= row*cols + col;
    }

}


int main(int argc, char **argv)
{
    
int **da = NULL;
    
int **ha = NULL;
    
int *dc = NULL;
    
int *hc = NULL;
    cudaError_t res;
    
int r, c;
    
bool is_right=true;

    res 
= cudaMalloc((void**)(&da), ROWS*sizeof(int*));CHECK(res)
    res 
= cudaMalloc((void**)(&dc), ROWS*COLS*sizeof(int));CHECK(res)
    ha 
= (int**)malloc(ROWS*sizeof(int*));
    hc 
= (int*)malloc(ROWS*COLS*sizeof(int));

    
for (r = 0; r < ROWS; r++)
    
{
        ha[r] 
= dc + r*COLS;
    }

    res 
= cudaMemcpy((void*)(da), (void*)(ha), ROWS*sizeof(int*), cudaMemcpyHostToDevice);CHECK(res)
    dim3 dimBlock(
16,16);
    dim3 dimGrid((COLS
+dimBlock.x-1)/(dimBlock.x), (ROWS+dimBlock.y-1)/(dimBlock.y));
    Kerneltest
<<<dimGrid, dimBlock>>>(da, ROWS, COLS);
    res 
= cudaMemcpy((void*)(hc), (void*)(dc), ROWS*COLS*sizeof(int), cudaMemcpyDeviceToHost);CHECK(res)

    
for (r = 0; r < ROWS; r++)
    
{
        
for (c = 0; c < COLS; c++)
        
{
            printf(
"%4d ", hc[r*COLS+c]);
            
if (hc[r*COLS+c] != (r*COLS+c))
            
{
                is_right 
= false;
            }

        }

        printf(
"\n");
    }

    printf(
"the result is %s!\n", is_right? "right":"false");
    cudaFree((
void*)da);
    cudaFree((
void*)dc);
    free(ha);
    free(hc);
    getchar();
    
return 0;
}

 


在CUDA中使用二维数组的几点说明:

1)da是一个二维变量,一定更不可以在33行的时候把ha改成da!一定要记住显存和内存是相互独立的,主机端的程序不可以直接操作显存!必须通过CUDA 提供的API函数来操作!

2)注意在内存申请时强制类型转换(void**)(&),怎么把***的变量转成**了!!这主要是API借口决定的,最好自己显式转换格式,避免不必要的麻烦。

3)看见数据拷贝的函数了吗,类型、类型、还是类型。

4)别忘了释放内存和显存!看见没,还是类型。

5)很希望这篇博客能帮到大家,可是我真的不推荐大家在GPU上使用二维数组!真的!!为什么呢?终归是效率惹的祸!显存的访问总是慢的。二维访存,可是连续访问了两次啊。要是老这样做,不但执行效率低,而且写代码也慢。如果对内存的概念不熟悉,千万别趟这趟浑水。看懂这段代码,就当是学习一下或者理解下内存、显存与内存独立的概念和规则吧。

附上执行结果:


 


posted @ 2012-10-21 12:43 jackdong 阅读(646) | 评论 (0)编辑 收藏

http://blog.csdn.net/bendanban/article/details/7753995
刚学了一招,可以使用编译器的-D选项来定义程序中使用的宏。
#include <stdio.h>
int main(int argc, char **argv)
{
    #ifdef MY_MAC
    printf(
"Hello -D.\n");
    
#else 
    printf(
"MY_MAC was not defined.\n");
    
#endif
    
return 0;
}
上面的代码中使用了MY_MAC宏,

 

【】$g++ -DMY_MAC -o dtest dtest.c

执行结果:

【】$ ./dtest 
Hello 
-D.
【】$

无掉-D选项,重新编译,执行结果:

【】$ g++ ./dtest.c -o dtest
【】$ .
/dtest 
MY_MAC was not defined.

我们可以利用编译器这个选项来调试我们的程序奥。

 

如果我的宏代表一个常量怎么办呢??

看看修改后的代码:

 

#include <stdio.h>
int main(int argc, char **argv)
{
    #ifdef MY_MAC
    printf(
"Hello -D. %d\n", MY_MAC);
    
#else 
    printf(
"MY_MAC was not defined.\n");
    
#endif
    
return 0;
}



我输出了宏代表的值。

 

在终端执行一下命令:

 

【】$ g++ -DMY_MAC=5 ./dtest.c -o dtest
【】$ .
/dtest 
Hello 
-D. 5
【】$

如果程序中有多个宏可以这样编译

【】$g++ -DMAC1=5 -DMAC2=6 soucefile.c

^_^,,很兴奋是吧??这样我们就不用在代码里修改宏变量了。。




posted @ 2012-10-21 12:41 jackdong 阅读(294) | 评论 (0)编辑 收藏

http://blog.csdn.net/bendanban/article/details/7742593

在网上找了找关于Progfile的工具,找到了这篇文章觉得不错,转来分享下。对原文修改了下。

本文介绍了如何使用Gnu gprof 对Linux平台下的现有程序进行优化分析和生成程序调用图。主要偏重于对生成和使用流程图作介绍.

Gprof 简介:
Gprof功能:打印出程序运行中各个函数消耗的时间,可以帮助程序员找出众多函数中耗时最多的函数。产生程序运行时候的函数调用关系,包括调用次数,可以帮助程序员分析程序的运行流程。
有了函数的调用关系,这会让开发人员大大提高工作效率,不用费心地去一点点找出程序的运行流程,这对小程序来说可能效果不是很明显,但对于有几万,几十万代码量的工程来说,效率是毋庸置疑的!而且这个功能对于维护旧代码或者是分析Open Source来说那是相当诱人的,有了调用图,对程序的运行框架也就有了一个大体了解,知道了程序的“骨架“,分析它也就不会再那么茫然,尤其是对自己不熟悉的代码和Open Source。费话不多说了,让我们开始我们的分析之旅吧!

Gprof 实现原理:
通过在编译和链接你的程序的时候(使用 -pg 编译和链接选项),gcc 在你应用程序的每个函数中都加入了一个名为mcount ( or  “_mcount”  , or  “__mcount” , 依赖于编译器或操作系统)的函数,也就是说你的应用程序里的每一个函数都会调用mcount, 而mcount 会在内存中保存一张函数调用图,并通过函数调用堆栈的形式查找子函数和父函数的地址。这张调用图也保存了所有与函数相关的调用时间,调用次数等等的所有信息。

Gprof基本用法:
1. 使用 -pg 编译和链接你的应用程序。

2. 执行你的应用程序使之生成供gprof 分析的数据。

3. 使用gprof 程序分析你的应用程序生成的数据。

Gprof 简单使用:
让我们简单的举个例子来看看Gprof是如何使用的。

1.打开linux终端。新建一个test.c文件,并生用-pg 编译和链接该文件。 test.c 文件内容如下:
#include "stdio.h"
#include 
"stdlib.h"
void a()
{
  printf(
"\t\t+---call a() function\n");
}

void c()
{
  printf(
"\t\t+---call c() function\n");
}

int b()
{
  printf(
"\t+--- call b() function\n");
  a();
  c();
  
return 0;
}


int main()
{
  printf(
" main() function()\n");
  b();
}

命令行里面输入下面命令,没加-c选项,gcc 会默认进行编译并链接生成a.out:
$gcc -pg test.c

如果没有编译错误,gcc会在当前目录下生成一个a.out文件,当然你也可以使用 –o 选项给生成的文件起一个别的名字,像 gcc –pg test.c –o test , 则gcc会生成一个名为test的可执行文件,在命令行下输入[linux /home/test]$./test , 就可以执行该程序了,记住一定要加上 ./ 否则程序看上去可能是执行,可是什么输出都没有。

 

2.执行你的应用程序使之生成供gprof 分析的数据。  命令行里面输入:

[linux /home/test]$a.out
main() function()
+--- call b() function
+---call a() function
+---call c() function

你会在当前目录下看到一个gmon.out 文件, 这个文件就是供gprof 分析使用的。

 

3.使用gprof 程序分析你的应用程序生成的数据。
命令行里面输入:

 

$ gprof -b a.out gmon.out | less

由于gprof输出的信息比较多,这里使用了 less 命令,该命令可以让我们通过上下方向键查看gprof产生的输出,| 表示gprof -b a.out gmon.out 的输出作为 less的输入。下面是我从gprof输出中摘抄出的与我们有关的一些详细信息。

%     cumulative    self              self     total
time   seconds     seconds    calls  Ts
/call  Ts/call  name
0.00      0.00     0.00        1     0.00     0.00  a
0.00      0.00     0.00        1     0.00     0.00  b
0.00      0.00     0.00        1     0.00     0.00  c

Call graph

granularity: each sample hit covers 
4 byte(s) no time propagated

index 
% time    self  children    called     name
0.00    0.00       1/1           b [2]
[
1]      0.0    0.00    0.00       1         a [1]
-----------------------------------------------
0.00    0.00       1/1           main [10]
[
2]      0.0    0.00    0.00       1         b [2]
0.00    0.00       1/1           a [1]
0.00    0.00       1/1           c [3]
-----------------------------------------------
0.00    0.00       1/1           b [2]
[
3]      0.0    0.00    0.00       1         c [3]

从上面的输出我们能明显的看出来,main 调用了 b 函数, 而b 函数分别调用了a 和 c 函数。由于我们的函数只是简单的输出了一个字串,故每个函数的消耗时间都是0 秒。

 


常用的Gprof 命令选项解释:

-b不再输出统计图表中每个字段的详细描述。

-p 只输出函数的调用图(Call graph 的那部分信息)。

-q 只输出函数的时间消耗列表。

-E Name不再输出函数Name 及其子函数的调用图,此标志类似于 -e 标志,但它在总时间和百分比时间的计算中排除了由函数Name 及其子函数所用的时间。

-e Name 不再输出函数Name 及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个 -e 标志。一个 -e 标志只能指定一个函数。

-F Name 输出函数Name 及其子函数的调用图,它类似于 -f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个 -F 标志。一个 -F 标志只能指定一个函数。-F 标志覆盖 -E 标志。

-f Name输出函数Name 及其子函数的调用图。可以指定多个 -f 标志。一个 -f 标志只能指定一个函数。

-z 显示使用次数为零的例程(按照调用计数和累积时间计算)。

到这为止你可能对gprof 有了一个比较感性的认识了,你可能会问如何用它去分析一个真正的Open Source 呢!下面就让我们去用gprof去分析一个Open Source,看看如何去在真实的环境中使用它。

使用Gprof 分析 Cflow开源项目
CFlow 是程序流程分析工具,该工具可以通过分析C源代码,产生程序调用图!有点跟Gprof差不多,不过CFlow是通过源代码进行的静态分析并且 不能分析C++ 程序,你可以到http://www.gnu.org/software/cflow/去下载源代码。

假设你已经下载了该源代码(cflow-1.1.tar.gz),并把它放置在/home目录下,让我们看看如何在这个应用上使用gprof。

1. 使用 -pg 编译和链接该应用程序,请输入下列命令。

 

[linux /home/]tar zxvf cflow-1.1.tar.gz
[linux /home/cflow-1.1/src]$./configure
[linux /home]$make CFLAGS=-pg LDFLAGS=-pg 

如果没有出错你会在/home/cflow-1.1/src 目录下发行一个名为cflow的可执行文件,这就是我们加入-pg编译选项后编译出来的可以产生供gprof提取信息的可执行文件。记住一定要在编译和链接的时候都使用-pg选项,否则可能不会产生gmon.out文件。对于cflow项目,CFLAGS=-pg 是设置它的编译选项,LDFLAGS=-pg是设置它的链接选项。当然你也可以直接修改它的Makefile来达到上述相同的目的,不过一定要记住编译和链接都要使用-pg选项。

 

2. 运行cflow 程序使之生成gmon.out 文件供gprof使用。

 

[linux /home/cflow-1.1/src]$cflow parser.c

查看/home/cflow-1.1/src目录下有没有产生gmon.out文件,如果没有请重复第一步,并确认你已经在编译和链接程序的时候使用了-pg 选项。Cflow的使用请参考http://www.gnu.org/software/cflow/manual/cflow.html

 

3. 使用gprof分析程序
[linux /home/cflow-1.1/src]$gprof -b cflow gmon.out | less
恭喜你,不出意外你会在屏幕上看到gprof的输出,函数消耗时间和函数调用图,下面是我从我的输出中摘抄出来的一小段。

 

%   cumulative   self              self     total time   seconds   seconds    calls  Ts/call  Ts/call  name 0.00      0.00     0.00   118262     0.00     0.00  include_symbol 0.00      0.00     0.00    92896     0.00     0.00  is_printable 0.00      0.00     0.00    28704     0.00     0.00  set_level_mark 0.00      0.00     0.00    28703     0.00     0.00  is_last 0.00      0.00     0.00    19615     0.00     0.00  auto_processor 0.00      0.00     0.00    15494     0.00     0.00  gnu_output_handler 0.00      0.00     0.00    12286     0.00     0.00  delete_parm_processor 0.00      0.00     0.00     7728     0.00     0.00  newline 0.00      0.00     0.00     7728     0.00     0.00  print_function_name 0.00      0.00     0.00     7728     0.00     0.00  print_level

。。。。。。 。。。。。。

Call graph

granularity: each sample hit covers 4 byte(s) no time propagated

index % time    self  children   called     name [1]      0.0    0.00    0.00     79+855  [1] 0.00    0.00    166     dcl [52] 0.00    0.00    163     parse_dcl [53] 0.00    0.00    150     dirdcl [56] 0.00    0.00    129     parse_declaration [63] 0.00    0.00     98     parse_variable_declaration [66] 0.00    0.00     63     maybe_parm_list [69] 0.00    0.00     63    parse_function_declaration [70] 0.00    0.00     39    func_body [74]

。。。。。。 。。。。。。

 


通过分析%time你就知道了那个函数消耗的时间最多,你可以根据这个输出信息做有目的的优化,不过cflow执行的速度是在是太快了,以至%time都是0 (消耗时间是以秒为单位进行统计的)。

 

生成图形化的函数调用图

1.Graphviz 工具
看到这里你也可能觉得上面的函数调用图实在是不方便察看,也看不出来一个程序调用的整体框架。没有关系,我再介绍一个有用的工具给你,使用 Graphviz,Graphviz or Graph Visualization 是由 AT&T 开发的一个开源的图形可视化工具。它提供了多种画图能力,但是我们重点关注的是它使用 Dot 语言直连图的能力。在这里,将简单介绍如何使用 Dot 来创建一个图形,并展示如何将分析数据转换成 Graphviz 可以使用的规范, Dot 使用的图形规范。

使用 Dot 语言,你可以指定三种对象:图、节点和边。为了让你理解这些对象的含义,我将构建一个例子来展示这些元素的用法。


下图给出了一个简单的定向图(directed graph),其中包含 3 个节点。第一行声明这个图为 G,并且声明了该图的类型(digraph)。接下来的三行代码用于创建该图的节点,这些节点分别名为 node1、node2 和 node3。节点是在它们的名字出现在图规范中时创建的。边是在在两个节点使用边操作(->)连接在一起时创建的,如第 6 行到第 8 行所示。我还对边使用了一个可选的属性 label,用它来表示边在图中的名称。最后,在第 9 行完成对该图规范的定义。
使用 Dot 符号表示的示例图(test.dot)

 

digraph G {
    node1;
    node2;
    node3;

    node1 
-> node2 [label="edge_1_2"];
    node1 
-> node3 [label="edge_1_3"];
    node2 
-> node3 [label="edge_2_3"];
 }

要将这个 .dot 文件转换成一个图形映像,则需要使用 Dot 工具,这个工具是在 Graphviz 包中提供的。清单 6 介绍了这种转换。
清单 6. 使用 Dot 来创建 JPG 映像
[linux /home]$ dot -Tjpg test.dot -o test.jpg
在这段代码中,我告诉 Dot 使用 test.dot 图形规范,并生成一个 JPG 图像,将其保存在文件 test.jpg 中。所生成的图像如图1所示。在此处,我使用了 JPG 格式,但是 Dot 工具也可以支持其他格式,其中包括 GIF、PNG 和 postscript等等。

 


Dot创建的实例图

Dot 语言还可以支持其他一些选项,包括外形、颜色和很多属性。有兴趣可以查看graphviz相关文档。
2.从gprof的输出中提取调用图信息,产生可供Graphviz使用的dot文件。
这样的脚本有人已经实现了,我们只要下载一个现成的就可以了,首先从http://www.ioplex.com/~miallen/ 网站下载一个mkgraph脚本。解压该脚本到包含gmon.out文件的目录下。使用mkgraph0.sh产生调用的jpg图像文件。例如:使用上面的例子,生成cflow的调用图。
[linux /home/cflow-1.1/src]$ mkgraph0.sh cflow gmon.out
部分调用图如下,有了这个图是不是对程序整体框架有了个清晰地了解,如果你对生成的调用图效果不满意,你还可以通过修改mkgraph0脚本使之产生合适的dot文件即可:

总结:
使用gprof , Graphviz , mkgraph 生成函数调用图
1. 使用 -pg 编译和链接你的应用程序。
2. 执行你的应用程序使之生成供gprof 分析的数据。
3. 使用mkgraph脚本生成图形化的函数调用图。

相关资料:
文档:用 Graphviz 可视化函数调用
文档:Speed your code with the GNU profiler
文档:gropf 帮助文件
Mkgraph 脚本:http://www.ioplex.com/~miallen/
Graphviz 工具:http://www.graphviz.org
Cflow         :http://www.gnu.org/software/cflow/
(责任编辑:城尘 68476636-8003)

原文地址:点击打开链接http://os.51cto.com/art/200703/41426_2.htm








posted @ 2012-10-21 12:36 jackdong 阅读(474) | 评论 (0)编辑 收藏

http://blog.csdn.net/bendanban/article/details/6303282

在上一节中我们讲的是一个常用的并行化编程方法(for的并行化),其实它只是并行化编程的一个特例,只是它的地位较高,或者说它比其它并行化更重要。在本节中我们将讨论一般的并行区域编程。

并行区域的编译指导语句一般格式

    一般格式:#pragma omp parallel [clause[clause]…]{…}

    其中{…}中为每个线程都执行的部分,在parallel后面可以跟随一些指导子句,例如:threadprivate、copyin等,本节中将以一些实例来依次讲解这些语句的作用。

    时刻记住,主线程就是0号线程,这个与for不同,另外private、firstprivate、lastprivate是for并行的东西最好不要拿来比较,尽管我在本文里比较了,但是我还是感觉有些头痛啊。

例子实例

例1 不带指导子句的并行程序

      #pragma omp parallel指示它下面的一对大括号内的程序复制执行threads个数次。默默人情况下,所有并行区域中的变量是共享的,所以一定要谨防数据竞争的发生。


 

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    #pragma omp parallel 
    
for (int i = 0; i < 2; i++
    

        printf(
"Hello World! i = %d, Thread Num = %d/n", i, omp_get_thread_num()); 
    }
 
    
return 0
}

 

image

图1、例1的执行结果

例1的执行结果可以看出四个线程分别执行了一遍#pragma omp parallel下面的语句,想想一下如果使用#pragma omp parallel for会是什么样的结果。

例2 使用threadprivate子句

     此命令表示所有并行线程使用指定变量为各自私有的,能被定义为各线程私有变量的变量只能是静态变量和全局变量。看下面的程序,希望你能发现,其实#pragma omp threadprivate()是可以单独使用的。

    说明一下:被定义为threadprivate的变量对于每个线程永远是活的,你在任何时候再次重新启动各线程时,前面并行时最后得到的变量值仍然保留他的值,如果你用private代替threadprivate,那么你重新启动各线程时,变量的值仍然为0。如果你不初始化要声明为private的变量,那么在串行程序中,你就不能再没有任何赋值的情况下使用它,否则会引起异常的(VS2010)。而且private不能单独像threadprivate那样定义某个变量。

    如果你使用另外#pragma omp threadprivate(var),那么var必须是静态变量或者全局变量,如过你在并行开始之前并没有初始化赋值,那么每个线程中默认var的值为0.如果你赋了初值,那么每个线程中var的初值都是你赋的初始值。其中主线程就是0号线程。如果你使用了#pragma omp private(var),那么var并不会赋初值,即使你在并行之前赋了初值。想想原先#pragma omp parallel for 的firstprivate就知道了,呵呵。在#pragma omp parallel中可以使用lastprivate,但是不能使用lastprivate,使用firstprivate(var)后,串行部分的var并不是并行时0号线程的var值,它仍未你原先赋的初值。是不是很绕啊?呵呵,你可以这样理解,使用firstprivate和private的并行都不是纯真的联合主线程的编号。这样就混不了了。

 

#include 
#include 
"omp.h" 
int sum = 0
#pragma omp threadprivate(sum) 
int main(int argc, char* argv[]) 

    #pragma omp parallel 
    

        sum 
+= omp_get_thread_num(); 
        printf(
"parallel sum = %d; thread num = %d/n", sum, omp_get_thread_num()); 
    }
 
    printf(
"/nserial sum = %d; thread num = %d/n/n", sum, omp_get_thread_num()); 
    #pragma omp parallel 
    printf(
"parallel sum = %d; thread num = %d/n", sum, omp_get_thread_num()); 
    
return 0
}



 

image

图2、例2执行结果

threadprivate指定了sum这个变量属于每个线程,每个线程都有自己的sum变量,各个线程结束后他们的sum变量并没有注销,在此啊执行时,他们各自的sum值还保持原值。

例3 使用copyin

指定主线程的值拷贝到各线程中去,下面的例子中,sum是每个线程独有的,并且初始值都是0,main中第一行代码将0号线程的sum值赋为100,其它线程的sum值并没有变。

 

#include <stdio.h>
#include 
"omp.h" 
int sum; 
#pragma omp threadprivate(sum) 
int main(int argc, char* argv[]) 

    sum 
= 100
    #pragma omp parallel copyin(sum) 
    

        sum 
+= omp_get_thread_num(); 
        printf(
"parallel sum = %d; thread num = %d/n", sum, omp_get_thread_num()); 
    }
 
    printf(
"/nserial sum = %d; thread num = %d/n/n", sum, omp_get_thread_num()); 
    #pragma omp parallel 
    printf(
"parallel sum = %d; thread num = %d/n", sum, omp_get_thread_num()); 
    
return 0
}



 

image

图3、例3的执行结果

image

图4、例3中将copyin(sum)删除后的执行结果

通过以上图3、4可以看出copyin的用处是初始化各个线程中自己私有的sum变量。实际上定义sum为全局量时的初始值就是原sum值(如果不使用copyin的话)。

我想了想threadprivate和private以及firstprivate的区别。写出来大家讨论下。

1、threadprivate,限制变量为每个线程私有。被限制的变量必须具有全局特性,他的生命周期是整个程序。

2、private,可以限制变量为每个线程私有,但是他的生命周期是一次启动并行计算。

3、firstprivate,可以将穿行程序中的初值带进每个线程,变量为每个线程私有。生命周期与private相同。

4、还有个lastprivate的问题,他并不能在区域并行中使用。

大家实验把。。。

posted @ 2012-10-21 11:55 jackdong 阅读(354) | 评论 (0)编辑 收藏

http://blog.csdn.net/bendanban/article/details/6303100

   openMP并不是只能对循环来并行的,循环并行化单独拿出来说是因为它在科学计算中非常有用,比如向量、矩阵的计算。所以我单独拿出这一部分给大家讲讲。这里主要讲解的是for循环。

编译指导语句:

    一般格式:

    #pragma omp parallel for [clause[clause…]]

    for(index = first; qualification; index_expr)

    {…}

    第一句中[]的部分是可选的,由自己的程序并行特点而定。大家先不要把精力放到这里面。后面的文章中会继续讲解的。

并行化for的编写规则

    1、index的值必须是整数,一个简单的for形式:for(int i = start; i < end; i++){…} 。

    2、start和end可以是任意的数值表达式,但是它在并行化的执行过程中值不能改变,也就是说在for并行化执行之前,编译器必须事先知道你的程序执行多少次,因为编译器要把这些计算分配到不同的线程中执行。

    3、循环语句只能是单入口但出口的。这里只要你避免使用跳转语句就行了。具体说就是不能使用goto、break、return。但是可以使用continue,因为它并不会减少循环次数。另外exit语句也是可以用的,因为它的能力太大,他一来,程序就结束了。

例子讲解

例1、for循环并行:

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
int i; 
    #pragma omp parallel 
for 
    
for (i = 0; i < 12; i++
    
{        printf("i = %d  %d/n", i, omp_get_thread_num());    } 
    
return 0
}

 

例1的执行结果如图1所示:

image

图1、例1的执行结果

从结果中可以看出 i 属于{0,1,2}时由0号线程执行,i 属于{3,4,5}时由1号线程执行,i 属于{6,7,8}时由2号线程执行,i 属于{9,10,11}时由3号线程执行。omp_get_thread_num()这个函数通过执行结果大家也知道了,他返回每个线程的编号。

并行编译子句

    openMP中有多种并行化子句,这些子句都是为控制循环并行化编译而设定的,这里我们主要关注数据作用域子句,这里的数据作用域是指各个线程是否对某一变量有权访问。shared子句用来标记变量在各个线程之间是共享的,private子句标记变量在各个线程之间是私有的,实际上它会在在每个线程中保存一个副本。默认情况下,并行执行的变量是共享的。至于其它编译子句将在后面的文章中介绍。

用实例讲解数据作用域子句

实际上我很难想到一个综合的例子来讲解这种子句的限制异同,所以我写了几个例子。

例2、private

#include
#include "omp.h"
int main(int argc, char* argv[])
{
    float x = 4.3f;
    int i;
    #pragma omp parallel for private(x)
    for (i = 0; i < 12; i++)
    {
        x = 0;
        printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
    }
    printf("/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
    return 0;
}

image

图2、例2执行结果

例3 firstprivate(var):指定var在每个线程中都有一个副本,并且var的初始值在并行执行开始之前定义,每个并行线程的var的副本初值就是串行时定义的初始值。程序结束后串行程序中的var值并不会改变。

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
float x = 4.3f
    
int i; 
    #pragma omp parallel 
for firstprivate(x) 
    
for (i = 0; i < 12; i++
    

        x 
+= 1.0f
        printf(
"parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());        
    }
 
    printf(
"/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    
return 0
}

 

 

image

图3、例3的执行结果

例4 lastprivate(var):指定最后多线程执行完后原串行循环最后一次var的值带到主线程(串行部分)

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
float x = 4.3f
    
int i; 
    #pragma omp parallel 
for lastprivate(x) 
    
for (i = 0; i < 12; i++
    
{        
        x 
= 0.0f
        printf(
"parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());        
    }
 
    printf(
"/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    
return 0
}

image

图4、例4的执行结果

例5 firstprivate与lastprivate联用,很奇怪openMP很多情况下是不允许某个变量被指定两次规则的,他俩却可以,呵呵,而且配合效果还不错。

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
float x = 4.3f
    
int i; 
    #pragma omp parallel 
for firstprivate(x) lastprivate(x) 
    
for (i = 0; i < 12; i++
    
{        
        x 
+= (float)omp_get_thread_num(); 
        printf(
"parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());        
    }
 
    printf(
"/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    
return 0
}

image

图5、例5的执行结果

    从上面的例2、3的程序中可以看出例2中每个线程中x都是私有的,它属于每个线程,在主线程的定义并不能带入到各个线程中,使用firstprivate后,x在主线程的初始值可以带到各个线程中,在图3可以看出每个线程x的输出结果实际是相同的,但是在并行执行结束后,主线程中的x值仍然为4.3。从例4的执行结果可以看出最后x的值带出到了主线程中,这个x值到底是哪个线程中的哪?答案是最后一句x赋值后的值,哪个线程执行完的最晚就是哪个x的值。例5显示firstprivate与lastprivate联合使用的执行结果。

例6 reduction规约操作,

    执行reduction的变量要特别注意,以reduction(+:sum)为例。

    第一种情况:sum为局部变量。这是你必须为sum在串行程序中赋初值,sum 被设置为每个线程私有,先各自执行完算出各自的sum值,最后主线程会将 《线程数+1》个sum变量规约,比如你num_thread(4),在开始并行执行之前你对规约变量赋初值为10,并行时假设每个线程算的的sum值为1,那么最终sum带到串行程序中的变量值为14(串行的10+四个线程的1)。

    第二种情况:sum为全局变量。这是你不必为sum赋初始值,因为此时默认串行的sum值为0,进入每个线程的sum值也是0,规约时仍然是将《线程数+1》个sum值相加,因为你并没有对全局的sum赋初值,所以最后规约的结果看着像是只有各线程的sum参加了规约操作。其实当你将全局的sum赋初值时,你会发现最后规约的sum值又多加了全局变量sum的串行程序结果。

    重要提醒:不管你怎样设计sum的串行声明形式,只要他在被定义为规约变量,每次进入并行线程的sum值都是0;

    也许你想把每个并行线程的sum值初始化成一个非0的值,然后再各自线程中在使用,那么我可以告诉你,别想了(至少我没有做到)。因为我规约sum值,如果这个规约有意义你的每个线程应该是各自独立未回各自的sum的,那么这个初始值使用0就已经非常好了,因为各自的sum计算如果结果一样,你为何不直接用一句乘法哪(线程数*一个线程计算的sum值)。

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
float x = 0.0f
    
int i; 
    
float sum = 0.0f
#pragma omp parallel 
for private(x) reduction(+:sum) 
    
for (i = 0; i < 12; i++
    
{        
        x 
= (float)omp_get_thread_num(); 
        sum 
+= x; 
        printf(
"parallel sum = %.1f, thread nummber:%d/n", sum, omp_get_thread_num());        
    }
 
    printf(
"/nserial   sum = %.1f, thread nummber:%d/n", sum, omp_get_thread_num()); 
    
return 0
}

image

图6、例6执行结果

    在例6中我使用了reduction(+:sum),这表示每个线程对sum这个共享变量执行加操作时其它任何线程不能对它进行加操作,实际上我们这样理解是有偏差的,真正的机理在执行结果中不难看出,实际每个线程都拷贝了一个sum的副本,先在自己执行时加完sum,等所有线程都执行结束后,主线程再将每个线程的sum副本的值加起来返回给主线程中sum。

小结

    本节主要讲述了for语句的并行化。现在为止大家应该可以熟练使用for并行化了。文章中可能还有些不全面的地方,热切期望各位读者能给出批评和指正,期待中……


posted @ 2012-10-21 11:54 jackdong 阅读(588) | 评论 (0)编辑 收藏

http://blog.csdn.net/bendanban/article/details/6302857

 在学习并行编程之前,你应该知道进程、线程、主线程、从线程等基本概念。进程是一个大型应用程序的基本单位,在任务管理器里进程都有一个名称,后面跟随的是与他有关的资源。线程是程序执行的基本单位,它必须从属与一个进程,一个进程可以有多个线程,同一个进程的线程可以共享进程的资源,例如他们可以引用同一个变量的值。一个进程一般会与一个.EXE文件关联,所以我把程序和进程不加区分。一个程序中有多个线程时,它必然会有一个主线程,主线程执行完后,其它从线程也应该结束执行。

    并行化编程一般可以理解为多个线程的创建和并行化编程,并行化编程的东西很多,但他们都会有两个必须的规定:1、程序执行模型。2、存储模型。

    程序执行模型,他规定了并行化线程的执行方式,规则,或者说逻辑结构。openMP的执行采用了Fork-Join模型。主线程在执行过程中遇到要并行处理的部分,根据openMP的编译指导语句来创建,执行多个线程,创建的线程个数一般与计算机的核心数成正比,可以通过添加一个环境变量(OMP_NUM_THREADS)来规定创建线程的个数,注意环境变量添加后要注销或者重启系统才会生效。

image

图1 omp程序执行模型

    存储模型,omp针对的是一个计算机或者分布式计算机的并行,在一台计算机上他采用共享存储的方式,多个线程共享一块进程的内存资源。

    下面先写个程序例子,能让大家有个初步认识。这个程序是在VS2008中编译的,项目类型为Win32ConsoleApplication。

例1、并行HelloWorld程序:

#include <stdio.h>
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    printf(
"Hello World! Serial Begin./n"); 
    #pragma omp parallel    
//开始并行执行 
    {        printf("Hello World! Parallel/n");    } 
    printf(
"Hello World! Serial again./n"); 
    
return 0
}

 

此程序编译之前,还需要你对你的编译器项目属性设置一下。这里我们以VS2008为例,首先设置项目支持openMP。右击项目->属性->C/C++->语言->openMP支持修改为是,如图2所示,然后代码生成修改为多线程调试,如图3所示。执行结果如图4所示。

image

图2、添加openMP支持

image

图3、多线程调试支持

image

图4、执行结果

以上程序我并没有设置环境变量,因为我的计算机是双核的,所以他的并行部分输出了两行Hello World! Parallel,这说明他有两个线程执行并行部分,每个线程完全执行了相同的一段程序。我们在设置一下环境变量后在执行一下。顺便说明一下怎样设置环境变量。

计算机右击-》属性-》高级-》环境变量-》系统变量-》新建。。。如图5所示。

image

图5、Win7环境下设置环境变量

设置完环境后,注销或重启系统后,再次执行例1的程序后得到的结果中Hello World! Parallel 将被执行四遍。因为你已经设置了四个线程了。

到现在为止,大家可以模仿着例1写几个小程序了,可是还有一句话大家可能还不大明白吧,#pragma omp parallel这句话标记{}中的程序将在OMP_NUM_THREADS个线程中执行。

在下面的几篇文章中我将继续讲解openMP编程的基础知识。欢迎继续关注。


posted @ 2012-10-21 11:49 jackdong 阅读(352) | 评论 (0)编辑 收藏

仅列出标题
共10页: First 2 3 4 5 6 7 8 9 10