[6] CUDA之线程同步

news2024/11/23 17:40:43

CUDA之线程同步

  • 共享内存:线程时间需要互相交换数据才能完成任务的情况并不少见,因此,必须存在某种能让线程彼此交流的机制
  • 当很多线程并行工作并且访问相同的数据或者存储器位置的时候,线程间必须正确的同步
  • 线程之间交换数据并不一定要需要使用共享内存,只是共享内存较快而已

1.共享内存

  • 共享内存位于芯片内部,因此它比全局内存要快得多,相比没有经过缓存的全局内存访问,共享内存大约在延迟上第100倍
  • 同一个块中的线程可以访问相同的一段共享内存,不同块中的线程所见到的共享内存中的内容是不相同的
  • 如果某线程的计算结果在写入到共享内存完成之前被其他线程读取,那么将会导致错误。因此应该正确的控制和管理内存访问,这是由 __syncthreads() 指令完成的,该指令确保在继续执行程序之前完成对内存的所有写入操作,即同步,也被称为 barrierbarrier的含义是块中的所有线程都将到达该代码行,然后在此等待其他线程完成,当所有线程都到达了这里之后,他们可以一起继续往下执行
  • 举个例子:

#include <stdio.h>

//计算数组中当前元素之前所有元素的平均值
__global__ void gpu_shared_memory(float *d_a)
{
	// Defining local variables which are private to each thread
	int i, index = threadIdx.x;
	float average, sum = 0.0f;
	//定义共享内存并赋值
	__shared__ float sh_arr[10];
	sh_arr[index] = d_a[index];
	__syncthreads();    // This ensures all the writes to shared memory have completed
	for (i = 0; i<= index; i++) 
	{ 
		sum += sh_arr[i]; 
	}
	average = sum / (index + 1.0f);
	d_a[index] = average;
	sh_arr[index] = average;
}
  • 共享内存上的数字或者变量是通过__shared__修饰符定义的

  • 共享内存的大小应该等于每个块的线程数

  • 当数据从全局内存复制到共享内存时,需要保证所有线程都已经完成了它们的写入操作,并使用 __syncthreads() 进行一次同步

  • 主函数调用如下:

int main(int argc, char **argv)
{
	//Define Host Array
	float h_a[10];   
	//Define Device Pointer
	float *d_a;       
	
	for (int i = 0; i < 10; i++) {
		h_a[i] = i;
	}
	// allocate global memory on the device
	cudaMalloc((void **)&d_a, sizeof(float) * 10);
	// now copy data from host memory  to device memory 
	cudaMemcpy((void *)d_a, (void *)h_a, sizeof(float) * 10, cudaMemcpyHostToDevice);
	
	gpu_shared_memory << <1, 10 >> >(d_a);
	// copy the modified array back to the host memory
	cudaMemcpy((void *)h_a, (void *)d_a, sizeof(float) * 10, cudaMemcpyDeviceToHost);
	printf("Use of Shared Memory on GPU:  \n");
	//Printing result on console
	for (int i = 0; i < 10; i++) {
		printf("The running average after %d element is %f \n", i, h_a[i]);
	}
	return 0;
}

在这里插入图片描述

2. 原子操作

  • 原子操作的提出主要为了解决一个问题 -> 当大龄的线程需要试图修改一段较小的内存区域时引发的计算结果错误问题,尤其是在进行“读取 - 修改 - 写入” 操作序列的时候
  • 例如:假设某内存区域初始值为6,两个线程p和q分别试图将区域值+1,则最终的结果应该是8.但是在实际执行的时候,可能p和q两个线程同时读取了这个值,两个都得到了6,执行+1都得到了7,然后将7写入内存区域,这个正确结果8相比肯定是错误的
  • 下边给一个通过核函数进行多线程访问同一数组的小栗子(无原子操作)
#include <stdio.h>

//不同GPU算力不同,此处设置的参数也不相同,算例越高启用的块和线程数越大,实验现象越明显
#define NUM_THREADS 100000
#define SIZE  20

#define BLOCK_WIDTH 500

__global__ void gpu_increment_without_atomic(int* d_a)
{
	// Calculate thread id for current thread
	int tid = blockIdx.x * blockDim.x + threadIdx.x;
	// each thread increments elements wrapping at SIZE variable
	tid = tid % SIZE;
	d_a[tid] += 1;
}

int main(int argc, char** argv)
{
	printf("%d total threads in %d blocks writing into %d array elements\n",
		NUM_THREADS, NUM_THREADS / BLOCK_WIDTH, SIZE);
	// declare and allocate host memory
	int h_a[SIZE];
	const int ARRAY_BYTES = SIZE * sizeof(int);
	// declare and allocate GPU memory
	int* d_a;
	cudaMalloc((void**)&d_a, ARRAY_BYTES);
	//Initialize GPU memory to zero
	cudaMemset((void*)d_a, 0, ARRAY_BYTES);
	gpu_increment_without_atomic << <NUM_THREADS / BLOCK_WIDTH, BLOCK_WIDTH >> > (d_a);

	// copy back the array to host memory
	cudaMemcpy(h_a, d_a, ARRAY_BYTES, cudaMemcpyDeviceToHost);
	printf("Number of times a particular Array index has been incremented without atomic add is: \n");
	for (int i = 0; i < SIZE; i++)
	{
		printf("index: %d --> %d times\n ", i, h_a[i]);
	}

	cudaFree(d_a);
	return 0;
}
  • tid在计算过程中的变换范围可以退出最终数组计算结果的范围,针对上述代码可以知道最终计算的正确结果是每个值被执行了5000次+1,最终的正确结果应是5000,但是运行结果显示最终只被增加了十几次(每次计算结果随机的,初始值为0),这是因为很多线程同时读取同样的位置,然后增加到了同样的值,然后将它们保存到显存中,得到了错误的结果,错误结果如下:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 为了解决以上问题,CUDA提供了atomicAdd这种原子操作函数,该函数会从逻辑上保证,每个调用它的线程对相同的内存区域上的“读取旧值 - 累加 - 回写新值”操作时不可被其他线程扰乱的原子性的整体完成的
  • 使用atomicAdd 进行原子累加的内核函数如下:
#include <stdio.h>


#define NUM_THREADS 100000
#define SIZE  20
#define BLOCK_WIDTH 500

__global__ void gpu_increment_atomic(int* d_a)
{
	// Calculate thread id for current thread
	int tid = blockIdx.x * blockDim.x + threadIdx.x;

	// each thread increments elements wrapping at SIZE variable
	tid = tid % SIZE;
	atomicAdd(&d_a[tid], 1);
}

int main(int argc, char** argv)
{
	printf("%d total threads in %d blocks writing into %d array elements\n",
		NUM_THREADS, NUM_THREADS / BLOCK_WIDTH, SIZE);

	// declare and allocate host memory
	int h_a[SIZE];
	const int ARRAY_BYTES = SIZE * sizeof(int);

	// declare and allocate GPU memory
	int* d_a;
	cudaMalloc((void**)&d_a, ARRAY_BYTES);
	//Initialize GPU memory to zero
	cudaMemset((void*)d_a, 0, ARRAY_BYTES);
	gpu_increment_atomic << <NUM_THREADS / BLOCK_WIDTH, BLOCK_WIDTH >> > (d_a);

	// copy back the array to host memory
	cudaMemcpy(h_a, d_a, ARRAY_BYTES, cudaMemcpyDeviceToHost);
	printf("Number of times a particular Array index has been incremented is: \n");
	for (int i = 0; i < SIZE; i++)
	{
		printf("index: %d --> %d times\n ", i, h_a[i]);
	}

	cudaFree(d_a);
	return 0;
}

在这里插入图片描述

  • 如果测量一下运行时间,相比之前的那个简单的在全局内存上直接进行加法操作的程它用的时间更长,这是因为使用原子操作后程序具有更大的执行代价,但可以通过共享内存来加速这些原子累加操作

  • 如果线程规模不变,但原子操作的元素数量扩大,则这些同样次数的原子操作会更快的完成。这是因为更广泛的分布范围上的原子操作有利于利用多个能执行原子操作的单元,以及每个原子操作单元上面的竞争性的原子事务也相应减少了

  • ---- end----

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1699388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Django-auth组件

Django-auth组件 1 表结构 我们从python manage.py migrate为我们创建的auth组件内置的表开始看 auth_user&#xff1a;用户表存储用户信息&#xff08;登录admin后台&#xff09; 里面的字段分两类&#xff1a;用户基本信息&#xff08;用户名&#xff0c;邮箱&#xff0c;密…

音视频开发4-补充 FFmpeg 开发环境搭建 -- 在windows 上重新build ffmpeg

本节的目的是在windows 上 编译 ffmpeg 源码&#xff0c;这样做的目的是&#xff1a;在工作中可以根据工作的实际内容裁剪 ffmpeg&#xff0c;或者改动 ffmpeg 的源码。 第一步 &#xff1a;下载&#xff0c; 安装&#xff0c;配置 &#xff0c;运行 msys64 下载 下载地址&…

字符串的周期:每一期都有那么几位

【题目描述】 如果一个字符串可以由某个长度为k的字符串重复多次得到,则称该串以k为周期。例 如,abcabcabcabc以3为周期(注意,它也以6和12为周期)。 输入一个长度不超过80的字符串(不含空格),输出其最小周期。 输入第一行表示有T组数据,后续是T行字符串。输出的每组…

透视App投放效果,Xinstall助力精准分析,让每一分投入都物超所值!

在移动互联网时代&#xff0c;App的推广与投放成为了每一个开发者和广告主必须面对的问题。然而&#xff0c;如何精准地掌握投放效果&#xff0c;让每一分投入都物超所值&#xff0c;却是一个令人头疼的难题。今天&#xff0c;我们就来谈谈如何通过Xinstall这个专业的App全渠道…

python纯脚本搬砖DNF之深度学习,工作室适用

声明&#xff1a; 本文章仅作学习交流使用,对产生的任何影响&#xff0c;本人概不负责. 转载请注明出处:https://editor.csdn.net/md?articleId103674748 主要功能 脚本已初步完成&#xff0c;可以上机实战了 1.搬砖研究所、海伯伦&#xff08;持续更新中&#xff09; 2.自…

【揭秘!在线ChatGPT神器,体验入口在此!】

&#x1f680;【揭秘&#xff01;在线ChatGPT神器&#xff0c;体验入口在此&#xff01;】&#x1f680; 前言 嘿&#xff0c;大家好&#xff01;今天我要和大家分享一些关于如何使用免费的ChatGPT的技巧。ChatGPT是一项令人兴奋的人工智能技术&#xff0c;它可以成为我们的好…

【vue-2】v-on、v-show、v-if及按键修饰符

目录 1、v-on事件 2、按键修饰符 3、显示和隐藏v-show 4、条件渲染v-if 1、v-on事件 创建button按钮有以下两种方式&#xff1a; <button v-on:click"edit">修改</button> <button click"edit">修改</button> 完整示例代码…

AlexNet论文解析—ImageNet Classification with Deep Convolutional Neural Networks

AlexNet论文解析—ImageNet Classification with Deep Convolutional Neural Networks 2012 研究背景 认识数据集&#xff1a;ImageNet的大规模图像识别挑战赛 LSVRC-2012&#xff1a;ImageNet Large Scale Visual Recoanition Challenge 类别训练数据测试数据图片格式Mnist1…

Block-level Image Service for the Cloud——论文泛读

TOS 2024 Paper 论文阅读笔记整理 问题 企业越来越需要敏捷和弹性的计算基础设施来快速响应现实世界的情况。容器通过提供高效的基于流程的虚拟化和分层映像系统&#xff0c;可以实现灵活和弹性的应用程序部署。然而&#xff0c;由于图像下载和解包过程&#xff0c;创建或更新…

Web开发学习总结

学习路线 Web 全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站 初识Web前端 Web标准也称为网页标准&#xff0c;由一系列的标准组成&#xff0c;大部分由W3C(World Wide Web Consortium&#xff0c;万维网联盟)负责制定。三个组…

Qt pro工程文件编写汇总(区分debug和release、32位和64位的方法,编译输出目录等)

前言&#xff1a; 从事qt开发已经好几年了&#xff0c;但有关pro编写的一些细节问题一直没有一个很好的梳理汇总——因为实际工作开发中&#xff0c;往往只需要编译特定版本的软件&#xff08;例如32位release版本&#xff09;&#xff0c;项目创建好后并设置好编译路径&#x…

OSPF网络类型实验2

对R4 对R5&#xff0c;找R1注册 对R1宣告环回&#xff0c;再宣告一下tunnel接口 本实验不考虑区域划分 现在已经全部宣告完成 对R1&#xff0c;2&#xff0c;3改接口 broadcast工作方式hello时间10s&#xff0c;然后进行dr选举&#xff0c;由于2&#xff0c;3之间没有伪广播 …

【探索数据结构】线性表之双链表

&#x1f389;&#x1f389;&#x1f389;欢迎莅临我的博客空间&#xff0c;我是池央&#xff0c;一个对C和数据结构怀有无限热忱的探索者。&#x1f64c; &#x1f338;&#x1f338;&#x1f338;这里是我分享C/C编程、数据结构应用的乐园✨ &#x1f388;&#x1f388;&…

乡村振兴的农业品牌建设:打造农业品牌,提升农产品附加值,增强乡村经济竞争力,实现美丽乡村经济繁荣

目录 一、引言 二、农业品牌建设的重要性 &#xff08;一&#xff09;提升农产品附加值 &#xff08;二&#xff09;增强乡村经济竞争力 &#xff08;三&#xff09;实现美丽乡村经济繁荣 三、农业品牌建设的现状及问题 &#xff08;一&#xff09;现状 &#xff08;二…

Go微服务: Nacos的搭建和基础API的使用

Nacos 概述 文档&#xff1a;https://nacos.io/docs/latest/what-is-nacos/搭建&#xff1a;https://nacos.io/docs/latest/quickstart/quick-start-docker/有很多种搭建方式&#xff0c;我们这里使用 docker 来搭建 Nacos 的搭建 这里&#xff0c;我们选择单机模式&#xf…

java实现图书系统源码

建包和类: Book Book: package Book;public class Book {private String name;private String author;private int price;private String type;private boolean isBorrowed;public Book(String name, String author, int price, String type) {this.name name;this.author …

【Qnx 】Qnx IPC通信PPS

Qnx IPC通信PPS Qnx自带PPS服务&#xff0c;PPS全称Persistent Publish/Subscribe Service&#xff0c;就是常见的P/S通信模式。 Qnx PPS的通信模式是异步的&#xff0c;Publisher和Subscriber也无需关心对方是否存在。 利用Qnx提供的PPS服务&#xff0c;Publisher可以通知多…

OrangePi KunPengPro | 开发板开箱测评之学习与使用

OrangePi KunPengPro | 开发板开箱测评之学习与使用 时间&#xff1a;2024年5月23日20:51:12 文章目录 OrangePi KunPengPro | 开发板开箱测评之学习与使用概述1.参考2.资料、工具3.使用3-1.通过串口登录系统3-2.通过SSH登录系统3-3.安装交叉编译工具链3-4.复制文件到设备3-5.第…

Android 使用 ActivityResultLauncher 申请权限

前面介绍了 Android 运行时权限。 其中&#xff0c;申请权限的步骤有些繁琐&#xff0c;需要用到&#xff1a;ActivityCompat.requestPermissions 函数和 onRequestPermissionsResult 回调函数&#xff0c;今天就借助 ActivityResultLauncher 来简化书写。 步骤1&#xff1a;创…

攻防世界[GoodRe]

攻防世界[GoodRe] 学到知识&#xff1a; 逆向的精髓&#xff1a;三分懂&#xff0c;七分蒙。TEA 算法快速识别&#xff08;蒙&#xff09;&#xff1a; 数据处理的形式&#xff1a;进入加密时的数据和加密结束后的数据&#xff0c;处理时数据的分组等等&#xff0c;都能用来…