[6] CUDA之线程同步

CUDA之线程同步

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

1.共享内存

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

#include <stdio.h>//计算数组中当前元素之前所有元素的平均值
__global__ void gpu_shared_memory(float *d_a)
{// Defining local variables which are private to each threadint 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 completedfor (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 Arrayfloat h_a[10];   //Define Device Pointerfloat *d_a;       for (int i = 0; i < 10; i++) {h_a[i] = i;}// allocate global memory on the devicecudaMalloc((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 memorycudaMemcpy((void *)h_a, (void *)d_a, sizeof(float) * 10, cudaMemcpyDeviceToHost);printf("Use of Shared Memory on GPU:  \n");//Printing result on consolefor (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 threadint tid = blockIdx.x * blockDim.x + threadIdx.x;// each thread increments elements wrapping at SIZE variabletid = 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 memoryint h_a[SIZE];const int ARRAY_BYTES = SIZE * sizeof(int);// declare and allocate GPU memoryint* d_a;cudaMalloc((void**)&d_a, ARRAY_BYTES);//Initialize GPU memory to zerocudaMemset((void*)d_a, 0, ARRAY_BYTES);gpu_increment_without_atomic << <NUM_THREADS / BLOCK_WIDTH, BLOCK_WIDTH >> > (d_a);// copy back the array to host memorycudaMemcpy(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 threadint tid = blockIdx.x * blockDim.x + threadIdx.x;// each thread increments elements wrapping at SIZE variabletid = 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 memoryint h_a[SIZE];const int ARRAY_BYTES = SIZE * sizeof(int);// declare and allocate GPU memoryint* d_a;cudaMalloc((void**)&d_a, ARRAY_BYTES);//Initialize GPU memory to zerocudaMemset((void*)d_a, 0, ARRAY_BYTES);gpu_increment_atomic << <NUM_THREADS / BLOCK_WIDTH, BLOCK_WIDTH >> > (d_a);// copy back the array to host memorycudaMemcpy(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.rhkb.cn/news/332558.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端:音频可视化(H5+js版本)

一、效果展示 HTML5JS实现一个简单的音频可视化 二、代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>音频可视化</title><style></style></head><body><divs…

构建高效的在线培训机构CRM应用架构实践

在当今数字化时代&#xff0c;在线培训已成为教育行业的重要趋势之一。为了提供更好的学习体验和管理服务&#xff0c;在线培训机构需要构建高效的CRM&#xff08;Customer Relationship Management&#xff09;应用架构。本文将探讨在线培训机构CRM应用架构的设计与实践。 一、…

golang创建式设计模式---工厂模式

创建式设计模式—工厂模式 目录导航 创建式设计模式---工厂模式1)什么是工厂模式2)使用场景3)实现方式4)实践案例5)优缺点分析 1)什么是工厂模式 工厂模式(Factory Method Pattern)是一种设计模式&#xff0c;旨在创建对象时&#xff0c;将对象的创建与使用进行分离。通过定义…

每日一题23:统计文本中单词出现的次数

一、每日一题 解答&#xff1a; import pandas as pd def count_occurrences(files: pd.DataFrame) -> pd.DataFrame:bull_cnt len(files[files[content].str.contains(r\sbull\s)])bear_cnt len(files[files[content].str.contains(r\sbear\s)])res_df pd.DataFrame({…

MySql基础(一)--最详细基础入门,看完就懂啦(辛苦整理,想要宝宝的赞和关注嘻嘻)

前言 希望你向太阳一样&#xff0c;有起有落&#xff0c;不失光彩~ 一、数据库概述 1. 什么是数据库 数据库就是存储数据的仓库&#xff0c;其本质是一个文件系统&#xff0c;数据按照特定的格式将数据存储起来&#xff0c;用户可以对数据库中的数据进行增加&#xff0c;修改&…

Python 小游戏——贪吃蛇

Python 小游戏——贪吃蛇 文章目录 Python 小游戏——贪吃蛇项目介绍环境配置代码设计思路1. 初始化和变量定义2. 创建游戏窗口和FPS控制器3. 初始化贪吃蛇和食物的位置4. 控制贪吃蛇的方向和分数5. 主游戏循环 难点分析源代码呈现代码结果 项目介绍 贪吃蛇游戏是一款通过上下…

Java核心:注解处理器

Java提供了一个javac -processor命令支持处理标注有特定注解的类&#xff0c;来生成新的源文件&#xff0c;并对新生成的源文件重复执行。执行的命令大概是这样的: javac -XprintRounds -processor com.keyniu.anno.processor.ToStringProcessor com.keyniu.anno.processor.Po…

【C++】二分查找算法:x的平方根

1.题目 2.算法思路 看到题目可能不容易想到二分查找。 这题考察我们对算法的熟练程度。 二分查找的特点&#xff1a;数组具有二段性(不一定有序)。 题目中没有数组&#xff0c;我们可以造一个从0到x的数组&#xff0c;然后利用二分查找找到对应的值即可。 3.代码 class S…

八种单例模式

文章目录 1.单例模式基本介绍1.介绍2.单例模式八种方式 2.饿汉式&#xff08;静态常量&#xff0c;推荐&#xff09;1.基本步骤1.构造器私有化&#xff08;防止new&#xff09;2.类的内部创建对象3.向外暴露一个静态的公共方法 2.代码实现3.优缺点分析 3.饿汉式&#xff08;静态…

如何查看热门GPT应用?

1、登陆chatgpt 2、访问 https://chatgpt.com/gpts 3、在该界面&#xff0c;可以搜索并使用image generator, Write For Me&#xff0c;Language Teature等热门应用。

【Qt 学习笔记】Qt窗口 | 菜单栏 | QMenuBar的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt窗口 | 菜单栏 | QMenuBar的使用及说明 文章编号&#xff1a;Qt 学习…

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…

Redis可视化工具:Another Redis Desktop Manager下载安装使用

1.Github下载 github下载地址&#xff1a; Releases qishibo/AnotherRedisDesktopManager GitHub 2. 安装 直接双击exe文件进行安装 3. 连接Redis服务 先启动Redis服务&#xff0c;具体启动过程可参考&#xff1a; Windows安装并启动Redis服务端&#xff08;zip包&#xff09…

从程序被SQL注入来MyBatis 再谈 #{} 与 ${} 的区别

缘由 最近在的一个项目上面&#xff0c;发现有人在给我搞 SQL 注入&#xff0c;我真的想说我那么点资源测试用的阿里云服务器&#xff0c;个人估计哈&#xff0c;估计能抗住他的请求。狗头.png 系统上面的截图 数据库截图 说句实在的&#xff0c;看到这个之后我立马就是在…

Nginx文件解析漏洞复现:CVE-2013-4547

漏洞原理 CVE-2013-4547漏洞是由于非法字符空格和截止符导致Nginx在解析URL时的有限状态机混乱&#xff0c;导致攻击者可以通过一个非编码空格绕过后缀名限制。假设服务器中存在文件1. jpg&#xff0c;则可以通过改包访问让服务器认为访问的为PHP文件。 漏洞复现 开启靶场 …

【数据结构】快速排序详解!

文章目录 1. 快速排序的非递归版本2. 快速排序2.1 hoare 版本一2.2 挖坑法 &#x1f427;版本二2.3 前后指针 版本三2.4 调用以上的三个版本的快排 3. 快速排序的优化 1. 快速排序的非递归版本 &#x1f192;&#x1f427;关键思路&#xff1a; &#x1f34e;① 参数中的begin…

呆马科技----构建智能可信的踏勘云平台

近年来&#xff0c;随着信息技术的快速发展&#xff0c;各个行业都在积极探索信息化的路径&#xff0c;以提升工作效率和服务质量。智慧踏勘云平台是基于区块链和大数据技术构建的全流程智慧可信踏勘解决平台。平台集远程视频、数据显示、工作调度、过程记录为一体&#xff0c;…

嵌入式进阶——舵机控制PWM

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 舵机信号线代码示例初始化PWM初始化UART打印日志初始化外部中断Extimain函数 舵机最早用于船舶上实现转向功能,由于可以通过程序连…

Spring从零开始学使用系列(四)之@PostConstruct和@PreDestroy注解的使用

如果各位老爷觉得可以&#xff0c;请点赞收藏评论&#xff0c;谢谢啦&#xff01;&#xff01; 文章中涉及到的图片均由AI生成 公众号在最下方&#xff01;&#xff01;&#xff01; 目录 1. 介绍 1.1 PostConstruct概述 1.2 PreDestroy概述 2. 基本用法 2.1 注册CommonAnn…

JS根据所选ID数组在源数据中取出对象

let selectIds [1, 3] // 选中id数组let allData [{ id: 1, name: 123 },{ id: 2, name: 234 },{ id: 3, name: 345 },{ id: 4, name: 456 },] // 源数据let newList [] // 最终数据selectIds.map((i) > {allData.filter((item) > {item.id i && newList.pus…