首页 > 编程语言 >parallel programming in CUDA C(GPU并行程序实现数组求和 & Julia set)

parallel programming in CUDA C(GPU并行程序实现数组求和 & Julia set)

时间:2025-01-14 21:03:06浏览次数:3  
标签:__ DIM set cuComplex 并行程序 float programming dev int

前言

我们这节会学习到:
Ⅰ.CUDA在实现并行性时采用的一种重要方式
Ⅱ.用CUDA C编写第一段并行代码

一、Summing vector

#define N 10

void add(int *a, int *b, int *c){
  int tid = 0; //这是第0个CPU,因此索引从0开始
  while(tid<N){
    c[tid] = a[tid] + b[tid];
    tid += 1; // 由于只有一个CPU,因此每次递增1
  }
}

int main(){
	int a[N],b[N],c[N];
	for(int i = 0; i < N; ++i){
		a[i] = -i;
		b[i] = i * i;
	}
	add(a, b. c);
	
	for(int i = 0; i < N; ++i){
		printf("%d + %d = %d\n", a[i], b[i], c[i]);
	}
	
	return 0;
}

这个很简单,就是一个循环求和:把数组a和b保存至c中。
啥意思嘛,不是说并行程序吗?
这是写一个C语言的简单的数组求和,我们以这个为底版,改造成一个GPU的并行版本哦。大家可以想一下怎么写,然后继续往下看哦。

二、GPU vector sums

先看一下,main函数

##define N 10
#include <stdio.h>

int main(void) {
	int a[N], b[N], c[N];
	int* dev_a, * dev_b, * dev_c;

	//在GPU上分配内存
	cudaMalloc((void**)&dev_a, N * sizeof(int));
	cudaMalloc((void**)&dev_b, N * sizeof(int));
	cudaMalloc((void**)&dev_c, N * sizeof(int));

	for (int i = 0; i < N; i++) {
		a[i] = -i;
		b[i] = i * i;
	}

	//将数组a,b复制到GPU
	cudaMemcpy(dev_a, a, N * sizeof(int), cudaMemcpyHostToDevice);
	cudaMemcpy(dev_b, b, N * sizeof(int), cudaMemcpyHostToDevice);

	add << <N, 1 >> > (dev_a, dev_b, dev_c);

	//将数组c从GPU复制到CPU
	cudaMemcpy(c, dev_c, N * sizeof(int), cudaMemcpyDeviceToHost);

	//显示结果
		for (int i = 0; i < N; ++i) {
			printf("%d + %d = %d\n", a[i], b[i], c[i]);
		}

	//释放GPU上分配的内存
	cudaFree(dev_a);
	cudaFree(dev_b);
	cudaFree(dev_c);

	return 0;
}

add函数

__global__ void add(int *a, int *b, int *c){
  int tid = blockIdx.x;  //该索引的数据
  if(tid < N)
  c[tid] = a[tid] + b[tid];
}

大家是不是注意到了,这次调用add是这样了:
add<<<N,1>>>(dev_a, dev_b, dev_c);
之前是 <<<1,1>>>
现在就为大家解密一个!
这里的第一个参数是代表在GPU的kernel中执行的parallel blocks(并行线程块) 的数目,我们把这些并行线程块叫做grid。
这就带来一个问题,我们如何知道现在运行的线程块是哪个呢?
int tid = blockIdx.x; //该索引的数据
大家看一下这里!
没看到定义啊?这是一个CUDA runtime内置的变量。
那为什么是x呢?大家可以去官网查一下,我们就用到x,其他的用不到哦。
然后GPU中会有N个add副本,tid的值从0 ~ N-1(就是blockIdx.x,也就是这里<<<N,1>>>设定的),分别执行。
if(tid < N) 如果没有这个条件,和C语言会有相似的结果:内存越界。
在这里插入图片描述

三、A fun example(Julia Set)

朱利亚集合可以由下式进行反复迭代得到:
对于固定的复数c,取某一z值(如z = z0),可以得到序列
这一序列可能反散于无穷大或始终处于某一范围之内并收敛于某一值。我们将使其不扩散的z值的集合称为朱利亚集合。
这是百度的,我们要用并行程序实现它。
老规矩先来一个CPU的版本:

1.CPU的Julia集

代码如下(示例):

#define DIM 1000

int main( void ) {
    CPUBitmap bitmap( DIM, DIM );
    unsigned char *ptr = bitmap.get_ptr();

    kernel( ptr );

    bitmap.display_and_exit();
}

我们创建了一个 bitmap,然后把它的指针传到kernel函数中。

void kernel( unsigned char *ptr ){
    for (int y=0; y<DIM; y++) {
        for (int x=0; x<DIM; x++) {
            int offset = x + y * DIM;
			
            int juliaValue = julia( x, y );
            // 像素数据的存储格式是 RGBA,对于每一个像素,其包含了 4 个字节分别对应 RGBA 这 4 个通道的数据,大家可以改变一下试试
            ptr[offset*4 + 0] = 255 * juliaValue;
            ptr[offset*4 + 1] = 0;
            ptr[offset*4 + 2] = 0;
            ptr[offset*4 + 3] = 255;
        }
    }
 }

遍历所有的点,掉用 julia 进行判断:属于集合返回1(红色),不属于返回0(黑色)。

int julia( int x, int y ) { 
    const float scale = 1.5;
    float jx = scale * (float)(DIM/2 - x)/(DIM/2);
    float jy = scale * (float)(DIM/2 - y)/(DIM/2);

    cuComplex c(-0.8, 0.156);
    cuComplex a(jx, jy);

    int i = 0;
    for (i=0; i<200; i++) {
        a = a * a + c;
        if (a.magnitude2() > 1000)
            return 0;
    }

    return 1;
}

我们首先将像素坐标转换为复数空间的坐标,为了将复平面的原点定位到图像中心,代码将像素位置移动了DIM/2(我们的图像范围是(DIM,DIM)),然后,为了确保图像的范围为-1.0到1.0,我们将图像的坐标缩放了DIM/2倍。就是说原来的点是(x,y),转换之后是( (DIM/2-x)/(DIM/2), (DIM/2-y) / (DIM/2) )。在计算处复空间中的点之后,需要判断这个点是否属于Julia集。通过迭代判断(本示例迭代200次,在每次迭代完成后,都会判断结果是否超过某个阈值),如果属于集合,就返回1,否则,返回0。
scale
从生成图像(比如最终想可视化出 Julia 集的样子)的角度来看,不同的scale值会影响图像的缩放程度以及细节呈现情况。较大的scale值可能会让图像看起来被拉伸、放大,能够展现出更多局部的细节特征(但也可能导致图像超出显示范围等问题,需要配合其他参数调整);较小的scale值则会让图像整体收缩,看到的是更宏观的样子,可能会丢失一些细节信息。
这个 cuComplex c(-0.8, 0.156); 是我们设定的,这样会生成的图片很有趣,大家自己改动一下,看看别的效果怎么样。

struct cuComplex {
    float   r;
    float   i;
    cuComplex( float a, float b ) : r(a), i(b)  {}
    float magnitude2( void ) { return r * r + i * i; }
    cuComplex operator*(const cuComplex& a) {
        return cuComplex(r*a.r - i*a.i, i*a.r + r*a.i);
    }
    cuComplex operator+(const cuComplex& a) {
        return cuComplex(r+a.r, i+a.i);
    }
};

2.GPU的Julia集

int main( void ) {
    DataBlock   data;
    CPUBitmap bitmap( DIM, DIM, &data );
    unsigned char    *dev_bitmap;

    cudaMalloc( (void**)&dev_bitmap, bitmap.image_size() );
    data.dev_bitmap = dev_bitmap;

    dim3    grid(DIM,DIM);
    kernel<<<grid,1>>>( dev_bitmap );

    cudaMemcpy( bitmap.get_ptr(), dev_bitmap,
                              bitmap.image_size(),
                              cudaMemcpyDeviceToHost );
                              
    cudaFree( dev_bitmap );
                              
    bitmap.display_and_exit();
}

我们最关心的是有多少并行线程运行在kernel,因为每个点都是独立计算,互不影响,这样就可以开启和我们所计算的点一样多的线程
运行在GPU的函数。我们提到过 blockIdx.x,但这次是坐标系,我们得用一个二维的表示方法:
dim3 grid(DIM,DIM);
类型dim3并不是标准C定义的类型,它可以表是一个三维数组,至于为什么不直接用二维数组,CUDA开发人员主要是为了日后的扩展,所以用三维数组来表示二维数组,数组的第三维默认为1。下面的代码将线程块grid传递给CUDA运行时:
kernel<<<grid,1>>>(dev_bitmap);

__global__ void kernel( unsigned char *ptr ) {
    // map from blockIdx to pixel position
    int x = blockIdx.x;
    int y = blockIdx.y;
    int offset = x + y * gridDim.x;

    // now calculate the value at that position
    int juliaValue = julia( x, y );
    ptr[offset*4 + 0] = 255 * juliaValue;
    ptr[offset*4 + 1] = 0;
    ptr[offset*4 + 2] = 0;
    ptr[offset*4 + 3] = 255;
}
__device__ int julia( int x, int y ) {
    const float scale = 1.5;
    float jx = scale * (float)(DIM/2 - x)/(DIM/2);
    float jy = scale * (float)(DIM/2 - y)/(DIM/2);

    cuComplex c(-0.8, 0.156);
    cuComplex a(jx, jy);

    int i = 0;
    for (i=0; i<200; i++) {
        a = a * a + c;
        if (a.magnitude2() > 1000)
            return 0;
    }

    return 1;
}
struct cuComplex {
    float   r;
    float   i;
    // 这里要给给它的构造函数前面加个 __device__。问题就是 cuComplex 类的构造函数被定义为了一个 host 函数,然后在 device 函数 julia 上被调用了。本书的代码没有加,也许是以前版本的 CUDA 是允许这种行为?
    __device__ cuComplex( float a, float b ) : r(a), i(b)  {}
    __device__ float magnitude2( void ) {
        return r * r + i * i;
    }
    __device__ cuComplex operator*(const cuComplex& a) {
        return cuComplex(r*a.r - i*a.i, i*a.r + r*a.i);
    }
    __device__ cuComplex operator+(const cuComplex& a) {
        return cuComplex(r+a.r, i+a.i);
    }
};

这部分代码中使用了修饰符__device__,这代表代码将在GPU而不是主机上运行,由于这些函数已声明为__device__,因此只能从其他__device__函数或者__global__函数中调用它们。
出现的问题①

在这里插入图片描述

error LNK1104: 无法打开文件“glut64.lib:
看这个链接:https://blog.csdn.net/yogurt_/article/details/104110243

出现的问题②
在这里插入图片描述
找不到dll的话:
看这个连接:https://blog.csdn.net/Algabeno/article/details/126666205

在这里插入图片描述

感谢上面两位大佬,才能让我在电脑上运行出这个程序来

标签:__,DIM,set,cuComplex,并行程序,float,programming,dev,int
From: https://blog.csdn.net/suy123/article/details/145084810

相关文章

  • C:\Users\Administrator\Local Settings\temp 是 Windows 操作系统中的一个临时文
    C:\Users\Administrator\LocalSettings\temp是Windows操作系统中的一个临时文件夹,通常用于存储操作系统和应用程序在运行时生成的临时数据。具体来说,temp文件夹用于存放临时文件,例如:安装文件:一些程序在安装过程中会将临时文件放在这里。缓存文件:一些程序可能会将数据缓存......
  • 【JAVA 基础 第(18)课】HashSet 使用方法详解
    HashSet:Set接口的实现类,存放无序的,不可重复的元素判断是否为重复的对象比较hashCode()方法的返回值,如果不同,判定为不同的对象,如果相同,执行第二步判断equals()方法的返回值,如果为true,则判为相同的对象,如果为false,则为不同的对象publicclassHashSetTest{ publicstatic......
  • 层次化综合中的uniquify和set_dont_touch问题
    在之前的博客里介绍过了层次化的综合方法。但在使用该方法时有一个特殊问题需要注意,对于需要多次例化的模块来说,直接设置set_dont_touch属性会导致pr阶段时工具报ununique问题。参考如下文章,可以知道问题的来源是如果Non_uniquified网表,多次实例化模块在网表中只有一个定义,而多......
  • 详解Redis的Set类型及相关命令
    目录SADDSMEMEBERSSISMEMBERSCARDSPOPSMOVESREMSINTERSINTERSTORESUNIONSUNIONSTORESDIFFSDIFFSTORE内部编码应用场景集合类型是保存多个字符串类型的元素的,但和列表类型不同的是,集合中元素之间是⽆序的,且元素不允许重复。⼀个集合中最多可以存储个元素。......
  • VP Toyota Programming Contest 2024#12(AtCoder Beginner Contest 384)
    A-aaaadaa题意:给你一个字符串和两个字符\(c_1\),\(c_2\),把字符串里的所有不等于\(c_1\)的字符都换成\(c_2\)。模拟即可。点击查看代码voidsolve(){intn;chara,b;std::cin>>n>>a>>b;std::strings;std::cin>>s;for(auto&c:......
  • NfcF.setTimeout
    NfcF.setTimeout(Objectobject)基础库2.11.2开始支持,低版本需做兼容处理。以Promise风格调用:不支持小程序插件:支持微信iOS版:不支持微信Android版:支持相关文档:近场通信(NFC)功能描述设置超时时间参数Objectobject属性类型默认值必填说明......
  • [PCIE5.0] 4.2.4.9 Reset
    本小节主要描述了两种重置机制:基础重置(FundamentalReset)和热重置(HotReset),以及它们在PCIe协议中如何影响接收端(Receiver)和发送端(Transmitter)的行为。1.基础重置(FundamentalReset)•基础重置是PCIe系统中一种重要的重置操作,它通常是在系统启动时进行,或者在需要恢复系统......
  • Set 系列集合的深入理解与应用
    在Java中,Set系列集合是一个非常重要的集合框架,它提供了多种实现,每个实现都具有独特的特性,适用于不同的场景。本文将详细介绍Set系列集合的相关知识,包括其通用特性、各种实现类及其底层原理。一、Set集合的通用特性Set集合具有以下几个显著的特点:无序:这意味着元素的存......
  • Towards Better Multi-task Learning: A Framework for Optimizing Dataset Combinati
    本文是LLM系列文章,针对《TowardsBetterMulti-taskLearning:AFrameworkforOptimizingDatasetCombinationsinLargeLanguageModels》的翻译。迈向更好的多任务学习:一个优化大型语言模型中数据集组合的框架摘要1引言2相关工作3框架4实验设置5结果6......
  • 请说说setData的操作过程
    setData的操作过程在前端开发中,特别是在小程序开发中,扮演着至关重要的角色。它是将数据从逻辑层发送到视图层并进行更新的关键步骤。以下是setData操作过程的详细解释:一、准备数据在逻辑层中,开发者需要准备好要更新的数据。这些数据通常以对象的形式存在,其中键(key)对应着要更新的......