AMD OpenCL大学课程(12) 性能优化案例NBody

    本节主要介绍NBody算法的OpenCL性能优化。

1、NBody

    NBody系统主要用来通过粒子之间的物理作用力来模拟星系系统。每个粒子表示一个星星,多个粒子之间的相互作用,就呈现出星系的效果。

 

   上图为一个粒子模拟星系的图片:Source: THE GALAXY-CLUSTER-SUPERCLUSTER CONNECTION,http://www.casca.ca/ecass/issues/1997-DS/West/west-bil.html

   由于每个粒子之间都有相互作用的引力,所以这个算法的复杂度是N2的。下面我们主要探讨如何优化算法以及在OpenCL基础上优化算法。

2、NBody算法

   假设两个粒子之间通过万有引力相互作用,则任意两个粒子之间的相互作用力F公式如下:

   最笨的方法就是计算每个粒子和其它粒子的作用力之和,这个方法通常称作N-Pair的NBody模拟。

   粒子之间的万有引力和它们之间的距离成反比,对于一个粒子而言(假设粒子质量都一样),远距离粒子的作用力有时候很小,甚至可以忽略。Barnes Hut 把3D空间按八叉树进行分割,只有在相邻cell的粒子才直接计算它们之间的引力,远距离cell中的粒子当作一个整体来计算引力。

3、OpenCL优化Nbody

     在本节中,我们不考虑算法本身的优化,只是通过OpenCL机制来优化N-Pair的NBody模拟。

     最简单的实施方法就是每个例子的作用力相加,代码如下:

for(i=0; i<n; i++){ ax = ay = az = 0;// Loop over all particles "j” for (j=0; j<n; j++) {

//Calculate Displacementdx=x[j]-x[i];dy=y[j]-y[i];dz=z[j]-z[i];

// small eps is delta added for dx,dy,dz = 0invr= 1.0/sqrt(dx*dx+dy*dy+dz*dz +eps);invr3 = invr*invr*invr;f=m[ j ]*invr3;

// Accumulate acceleration ax += f*dx; ay += f*dy;az += f*dx;}// Use ax, ay, az to update particle positions}

我们对每个粒子计算作用在它上面的合力,然后求在合力作用下,delta时间内粒子的新位置,并把这个新位置当作下次计算的输入参数。

没有优化的OpenCL kernel代码如下:

__kernel void nbody_sim_notile(__global float4* pos ,__global float4* vel,int numBodies,float deltaTime,float epsSqr,__local float4* localPos,__global float4* newPosition,__global float4* newVelocity)

{unsigned int tid = get_local_id(0);unsigned int gid = get_global_id(0);unsigned int localSize = get_local_size(0);

// position of this work-itemfloat4 myPos = pos[gid];float4 acc = (float4)(0.0f, 0.0f, 0.0f, 0.0f);

// load one tile into local memoryint idx = tid * localSize + tid;localPos[tid] = pos[idx];

// calculate acceleration effect due to each body// a[i->j] = m[j] * r[i->j] / (r^2 + epsSqr)^(3/2)for(int j = 0; j < numBodies; ++j){// Calculate acceleartion caused by particle j on particle ilocalPos[tid] = pos[j];float4 r = localPos[j] - myPos;float distSqr = r.x * r.x + r.y * r.y + r.z * r.z;float invDist = 1.0f / sqrt(distSqr + epsSqr);float invDistCube = invDist * invDist * invDist;float s = localPos[j].w * invDistCube;

// accumulate effect of all particlesacc += s * r;}

float4 oldVel = vel[gid];

// updated position and velocityfloat4 newPos = myPos + oldVel * deltaTime + acc * 0.5f * deltaTime * deltaTime;newPos.w = myPos.w;

float4 newVel = oldVel + acc * deltaTime;

// write to global memorynewPosition[gid] = newPos;newVelocity[gid] = newVel;}

在这种实现中,每次都要从global memory中读取其它粒子的位置,速度,内存访问= N reads*N threads= N2

我们可以通过local memory进行优化,一个粒子数据读进来以后,可以被p*p个线程共用,p*p即为workgroup的大小,对于每个粒子,我们通过迭代p*p的tile,累积得到最终结果。

优化后的kernel代码如下:

__kernel void nbody_sim(

__global float4* pos ,

__global float4* vel,

int numBodies,

float deltaTime,

float epsSqr,

__local float4* localPos,__global float4* newPosition,__global float4* newVelocity)

{unsigned int tid = get_local_id(0);

unsigned int gid = get_global_id(0);

unsigned int localSize = get_local_size(0);

// Number of tiles we need to iterate

unsigned int numTiles = numBodies / localSize;

// position of this work-item

float4 myPos = pos[gid];

float4 acc = (float4)(0.0f, 0.0f, 0.0f, 0.0f);

for(int i = 0; i < numTiles; ++i)

{

// load one tile into local memory

int idx = i * localSize + tid;

localPos[tid] = pos[idx];

// Synchronize to make sure data is available for processing

barrier(CLK_LOCAL_MEM_FENCE);

// calculate acceleration effect due to each body

// a[i->j] = m[j] * r[i->j] / (r^2 + epsSqr)^(3/2)

for(int j = 0; j < localSize; ++j)

{

// Calculate acceleartion caused by particle j on particle i

float4 r = localPos[j] - myPos;

float distSqr = r.x * r.x + r.y * r.y + r.z * r.z;

float invDist = 1.0f / sqrt(distSqr + epsSqr);

float invDistCube = invDist * invDist * invDist;

float s = localPos[j].w * invDistCube;

// accumulate effect of all particles

acc += s * r;

}

// Synchronize so that next tile can be loaded

barrier(CLK_LOCAL_MEM_FENCE);

}

float4 oldVel = vel[gid];

// updated position and velocity

float4 newPos = myPos + oldVel * deltaTime + acc * 0.5f * deltaTime * deltaTime;

newPos.w = myPos.w;

float4 newVel = oldVel + acc * deltaTime;

// write to global memory

newPosition[gid] = newPos;

newVelocity[gid] = newVel;}

下面是在AMD, NV两个平台上性能测试结果:

AMD GPU = 5870 Stream SDK 2.2

Nvidia GPU = GTX 480 with CUDA 3.1

另外,在程序中,也尝试了循环展开,通过展开内循环,从而减少GPU执行分支指令,我的测试中,使用展开四次,得到的FPS比没展开前快了30%。(AMD 5670显卡)。具体实现可以看kernel代码中的__kernel void nbody_sim_unroll函数。在AMD平台上,使用向量化也可以提高10%左右的性能。

最后提供2篇NBody优化的文章:

—Nvidia GPU Gems

http://http.developer.nvidia.com/GPUGems3/gpugems3_ch31.html

—Brown Deer Technology

http://www.browndeertechnology.com/docs/BDT_OpenCL_Tutorial_NBody.html

完整的代码可从:http://code.google.com/p/imagefilter-opencl/downloads/detail?name=amduniCourseCode7.zip&can=2&q=#makechanges 下载。

时间: 2024-08-03 05:43:18

AMD OpenCL大学课程(12) 性能优化案例NBody的相关文章

AMD OpenCL大学课程(11)

性能优化 1.线程映射    所谓线程映射是指某个线程访问哪一部分数据,其实就是线程id和访问数据之间的对应关系. 合适的线程映射可以充分利用硬件特性,从而提高程序的性能,反之,则会降低性能.    请参考Static Memory Access Pattern Analysis on a Massively Parallel GPU这篇paper,文中讲述线程如何在算法中充分利用线程映射.这是我在google中搜索到的下载地址:http://www.ece.neu.edu/~bjang/pat

AMD OpenCL大学课程(10)

GPU线程及调度      本节主要讲述OpenCL中的Workgroup如何在硬件设备中被调度执行.同时也会讲一下同一个Workgroup中的workitem,如果它们执行的指令发生diverage(就是执行指令不一致)对性能的影响.学习OpenCL并行编程,不仅仅是对OpenCL Spec本身了解,更重要的是了解OpenCL硬件设备的特性,现阶段来说,主要是了解GPU的的架构特性,这样才能针对硬件特性优化算法.现在OpenCL的Spec是1.1,随着硬件的发展,相信OpenCL会支持更多的并

AMD OpenCL大学课程(2)

1.OpenCL架构       OpenCL可以实现混合设备的并行计算,这些设备包括CPU,GPU,以及其它处理器,比如Cell处理器,DSP等.使用OpenCL编程,可以实现可移植的并行加速代码.[但由于各个OpenCL device不同的硬件性能,可能对于程序的优化还要考虑具体的硬件特性].    通常OpenCL架构包括四个部分: 平台模型(Platform Model) 执行模型(Execution Model) 内存模型(Memory Model) 编程模型(Programming

AMD OpenCL大学课程(6)

GPU架构 内容包括: 1.OpenCLspec和多核硬件的对应关系 AMD GPU架构 Nvdia GPU架构 Cell Broadband Engine 2.一些关于OpenCL的特殊主题 OpenCL编译系统 Installable client driver   首先我们可能有疑问,既然OpenCL具有平台无关性,我们为什么还要去研究不同厂商的特殊硬件设备呢? 了解程序中的循环和数据怎样映射到OpenCL Kernel中,便于我们提高代码质量,获得更高的性能. 了解AMD和Nvdia显卡

AMD OpenCL大学课程(5)

OpenCL内存模型     OpenCL的内存模型定义了各种各样内存类型,各种内存模型之间有层级关系.各种内存之间的数据传输必须是显式进行的,比如从host memory到device memory,从global memory到local memory等等.     WorkGroup被映射到硬件的CU上执行(在AMD 5xxx系列显卡上,CU就是simd,一个simd中有16个pe),OpenCL并不提供各个workgroup之间的一致性,如果我们需要在各个workgroup之间共享数据或

AMD OpenCL大学课程(7)

6.Nvdia GPU Femi架构 GTX480-Compute 2.0 capability: 有15个core或者说SM(Streaming Multiprocessors ). 每个SM,一般有32 cuda处理器. 共480个cuda处理器. 带ECC的global memory 每个SM内的线程按32个单位调度执行,称作warp.每个SM内有2个warp发射单元. 一个cuda核由一个ALU和一个FPU组成,FPU是浮点处理单元. SIMT和SIMD SIMT是指单指令.多线程. 硬

AMD OpenCL大学课程(3)

OpenCL内存对象:       OpenCL内存对象就是一些OpenCL数据,这些数据一般在设备内存中,能够被拷入也能够被拷出.OpenCL内存对象包括buffer对象和image对象. buffer对象:连续的内存块----顺序存储,能够通过指针.行列式等直接访问. image对象:是2维或3维的内存对象,只能通过read_image() 或 write_image()来读取.image对象可以是可读或可写的,但不能同时既可读又可写.        该函数会在指定的context上创建一个

AMD OpenCL大学课程(13) OpenCL扩展

1.OpenCL扩展      OpenCL扩展是指device支持某种特性,但这中特性并不是OpenCL标准的一部分.通过扩展,厂商可以给device增加一些新的功能,而不用考虑兼容性问题.现在各个厂商在OpenCL的实现中或多或少的使用了自己的扩展.      扩展的类型分为三种: Khronos OpenCL工作组批准的扩展,这种要经过一致性测试,可能会被增加到新版本的OpenCL规范中.这种扩展都以cl_khr作为扩展名. 外部扩展, 以cl_ext为扩展名.这种扩展是由2个或2个以上的

AMD OpenCL大学课程(4)

Kernel对象:     Kernel就是在程序代码中的一个函数,这个函数能在OpenCL设备上执行.一个Kernel对象就是kernel函数以及其相关的输入参数.   Kernel对象通过程序对象以及指定的函数名字创建.注意:函数必须是程序源代码中存在的函数. 运行时编译:     在运行时,编译程序和创建kernel对象是有时间开销的,但这样比较灵活,能够适应不同的OpenCL硬件平台.程序动态编译一般只需一次,而Kernel对象在创建后,可以反复调用.   创建Kernel后,运行Ker