首页 > 系统相关 >CUDA之矩阵转置(全局内存、共享内存)

CUDA之矩阵转置(全局内存、共享内存)

时间:2023-08-15 16:14:04浏览次数:39  
标签:__ 转置 32 threadIdx int 线程 CUDA 共享内存 bank

使用全局内存

完整代码链接

A合并访问、B非合并访问

#ifdef USE_DP
    typedef double real;
#else
    typedef float real;
#endif
__global__ void transpose1(const real *A, real *B, const int N)
{
    const int nx = blockIdx.x * blockDim.x + threadIdx.x;
    const int ny = blockIdx.y * blockDim.y + threadIdx.y;
    if (nx < N && ny < N)
    {
        // 对矩阵 A 中数据的访问(读取)是顺序的,但对矩阵 B 中数据的访问(写入)不是顺序的
        // nx表示第nx列,ny表示第ny行
        B[nx * N + ny] = A[ny * N + nx];
    }
}

A非合并访问、B合并访问

// 只读数据缓存的加载函数 __ldg()。从帕斯卡架构开始,如果编译器能够判断一个全局内存变量在
// 整个核函数的范围都只可读(如这里的矩阵 A),则会自动用函数 __ldg() 读取全局内存,
// 从而对数据的读取进行缓存,缓解非合并访问带来的影响

__global__ void transpose2(const real *A, real *B, const int N)
{
    const int nx = blockIdx.x * blockDim.x + threadIdx.x;
    const int ny = blockIdx.y * blockDim.y + threadIdx.y;
    if (nx < N && ny < N)
    {
        B[ny * N + nx] = A[nx * N + ny];
    }
}

使用共享内存

使用共享内存可以减少对全局内存的访问,加快数据的访问速度
完整代码链接

AB合并访问,存在bank

__global__ void transpose1(const real *A, real *B, const int N)
{
    __shared__ real S[TILE_DIM][TILE_DIM];
    int bx = blockIdx.x * TILE_DIM;
    int by = blockIdx.y * TILE_DIM;

    int nx1 = bx + threadIdx.x;     // 第nx1列
    int ny1 = by + threadIdx.y;     // 第ny1行
    if (nx1 < N && ny1 < N)
    {
        // A的访问是合并的, 第 11 行对共享内存的访问不导致 bank 冲突
        S[threadIdx.y][threadIdx.x] = A[ny1 * N + nx1];
    }
    __syncthreads();
    
    int nx2 = bx + threadIdx.y;     // 第bx块的y行 -》S的x行
    int ny2 = by + threadIdx.x;     // 第by块的x列  -》 S的y列
    if (nx2 < N && ny2 < N)
    {
        // 二维的情况:一般32个连续的threadIdx.x构成一个线程束,一个线程束内的threadIdx.y是相同的
        // 同一个线程束内的线程(连续的threadIdx.x)刚好访问同一个bank中的32个数据,这将导致 32 路 bank 冲突
        B[nx2 * N + ny2] = S[threadIdx.x][threadIdx.y];
    }
}

AB合并访问,消除bank

为了获得高的内存带宽,共享内存在物理上被分为 32 个(刚好等于一个线程束中的线程数目,即内建变量 warpSize 的值)同样宽度的、能被同时访问的内存 bank。我们可以将 32 个 bank 从 0 到 31 编号。在每一个 bank 中,又可以其中的内存地址从 0 开始编号。为方便起见,我们将所有 bank 中编号为 0 的内存称为第一层内存;将所有 bank 中编号为 1 的内存称为第二层内存。在开普勒架构中,每个 bank 的宽度为 8 字节;在所有其他架构中,每个 bank 的宽度为 4 字节

只要同一线程束内的多个线程不同时访问同一个 bank 中不同层的数据,该线程束对共享内存的访问就只需要一次内存事务(memory transaction)。当同一线程束内的多个线程试图访问同一个 bank 中不同层的数据时,就会发生 bank 冲突。在一个线程束内对同一个 bank 中的 n 层数据同时访问将导致 n 次内存事务,称为发生了 n 路 bank 冲突。最坏的情况是线程束内的 32 个线程同时访问同一个 bank 中 32 个不同层的地址,这将导致 32 路 bank 冲突。这种 n 很大的 bank 冲突是要尽量避免的。

__global__ void transpose2(const real *A, real *B, const int N)
{
    /*
        这样改变共享内存数组的大小之后,同一个线程束中的 32 个线程(连续的 32 个 threadIdx.x 值)将对应共享内存数组 S 中跨度为 33 的数据。如果第一个线程访问第一个 bank 的第一层,第二个线程则会
        访问第二个 bank 的第二层(而不是第一个 bank 的第二层);如此等等。于是,这 32 个线程将分别访问 32 个不同 bank 中的数据,所以没有 bank 冲突,
    */
    __shared__ real S[TILE_DIM][TILE_DIM + 1];
    int bx = blockIdx.x * TILE_DIM;
    int by = blockIdx.y * TILE_DIM;

    int nx1 = bx + threadIdx.x;
    int ny1 = by + threadIdx.y;
    if (nx1 < N && ny1 < N)
    {
        S[threadIdx.y][threadIdx.x] = A[ny1 * N + nx1];
    }
    __syncthreads();

    int nx2 = bx + threadIdx.y;
    int ny2 = by + threadIdx.x;
    if (nx2 < N && ny2 < N)
    {
        B[nx2 * N + ny2] = S[threadIdx.x][threadIdx.y];
    }
}


__shared__ real S[TILE_DIM][TILE_DIM + 1];
这样改变共享内存数组的大小之后,同一个线程束中的 32 个线程(连续的 32 个 threadIdx.x 值)将对应共享内存数组 S 中跨度为 33 的数据。如果第一个线程访问第一个 bank 的第一层,第二个线程则会访问第二个 bank 的第二层(而不是第一个 bank 的第二层);如此等等。于是,这 32 个线程将分别访问 32 个不同 bank 中的数据,所以没有 bank 冲突

参考:《CUDA 编程:基础与实践_樊哲勇》

标签:__,转置,32,threadIdx,int,线程,CUDA,共享内存,bank
From: https://www.cnblogs.com/xiaohuidi/p/17631489.html

相关文章

  • MemoryFile 共享内存原理分析
    Android上层提供了一些内存共享工具类,比如MemoryFile。你使用过吗?知道它的实现原理吗?MemoryFile是Java层对Ashmem的一个封装,下面来一起学习MemoryFile,掌握它的使用姿势和底层原理。MemoryFile使用方法大致如下:「进程A中申请一块共享内存写入数据,并准备好文件描述符:」Mem......
  • Linux 共享内存mmap,进程通信
    @TOC前言进程间通信是操作系统中重要的概念之一,使得不同的进程可以相互交换数据和进行协作。其中,共享内存是一种高效的进程间通信机制,而内存映射(mmap)是实现共享内存的一种常见方法。一、存储映射I/O存储映射I/O是一个磁盘文件与存储空间中的一个缓冲区相映射。于是,当从缓冲......
  • 《CUDA编程:基础与实践》读书笔记(5):统一内存编程
    统一内存(unifiedmemory)是一种逻辑上的概念,它既不是显存、也不是主机内存,而是CPU和GPU都可以访问并能保证一致性的虚拟存储器。使用统一内存对硬件有较高的要求:对于所有功能,GPU架构都必须不低于Kepler架构,主机应用程序必须为64位。对于一些较新的功能,至少需要Pascal架构的GPU......
  • CUDA cudaMemcpy函数总结
    在使用cuda的时候一定会用到cudaMemcpy这个函数,因为我们就是用它实现数据在CPU与GPU之间的移动,想在GPU端计算就必须要将数据从CPU拷贝到GPU,想要获得GPU的计算结果就必须将结果拷贝回CPU。但是在使用这个函数的时候对它的第一个参数存在一些疑惑,经过查找资料后做个简单的总结。首......
  • CUDA Memcpy的分析
    CUDAMemcpy是一种CUDA库中的函数,可以在主机内存和设备内存之间复制数据。本文将从功能、使用方法、性能、优化等多个角度详细介绍CUDAMemcpy。一、功能CUDAMemcpy的主要功能是在设备内存和主机内存之间进行数据传输。它可以将主机上的数据发送到GPU上,也可以将GPU上的数据传输到......
  • OpenCV与CUDA简介
    因为算法的需要,正常的CPU算法速度不够需要进行加速,OpenCV中正好加入了GPU计算的模块,OpenCV中有两种GPU的加速方式,一种是通用标准的opencl,另一种是NVIDIA的cuda加速。opencl是苹果公司提出的一种通用标准,多种平台支持的标准。cuda是NVIDIA提出的并行计算平台,只有NVIDIA的显卡支......
  • CUDA 简单程序的基本框架和自定义设备函数
    1cuda程序的基本框架框架包含:头文件常量或者宏定义C++自定义函数和cuda核函数的原型声明main函数C++自定义函数核CUDA核函数的定义实现其中main函数中1intmain()2{3分配主机与设备代码内存4初始化主机中的数据5将某些数据从主机复制到设备6调用核函数在设备......
  • 《CUDA编程:基础与实践》读书笔记(4):CUDA流
    1.CUDA流一个CUDA流指的是由主机发出的在一个设备中执行的CUDA操作序列。除主机端发出的流之外,还有设备端发出的流,但本文不考虑后者。一个CUDA流中的各个操作按照主机发布的次序执行;但来自两个不同CUDA流的操作不一定按照某个次序执行,有可能是并发或者交错地执行。任何CUDA操作......
  • CUDA 编程基础
    基于c/c++的编程方法支持异构编程的扩展方法简单明了的apis,能够轻松的管理存储系统cuda支持的编程语言:c/c++/python/fortran/java…1、CUDA并行计算基础异构计算CUDA安装CUDA程序的编写CUDA程序编译利用NVProf查看程序执行情况gpu不是单独的在计算机中完成任......
  • CUDA 配置环境(三):nvcc fatal : Could not set up the environment for Microsoft Visua
    解决在QT中编写CUDA程序出现nvccfatal:CouldnotsetuptheenvironmentforMicrosoftVisualStudiousing的问题问题详情在QT编写CUDA代码,在已经配好.pro文件中的代码,并且CUDA安装没有问题,还可以在VS2017中正常运行CUDA程序时,一开始debug的时候我遇到了以下问题:Could......