当前位置: 首页 > news >正文

cuda矩阵转置算子(共享内存)

cpu版本

即为按行遍历行列互换

// 主机上的矩阵转置函数,用于验证结果
void cpuTranspose(const float *input, float *output, int n)
{
    for (int row = 0; row < n; ++row)
    {
        for (int col = 0; col < n; ++col)
        {
            output[col * n + row] = input[row * n + col];
        }
    }
}

gpu未优化版本

每个线程单独处理一个元素

__global__ void transpose(float *input, float *output, int n)
{

    int row = blockDim.y * blockIdx.y + threadIdx.y;
    int col = blockDim.x * blockIdx.x + threadIdx.x;
    if (row < n && col < n)
    {

        output[col * n + row] = input[row * n + col];
    }
}

gpu共享内存

对齐访问:对齐访问顾名思义即当设备内存事务的第一个地址是用于事务服务的缓存粒度的偶数倍时(32字节的二级 缓存或128字节的一级缓存),就会出现对齐内存访问。运行非对齐的加载会造成带宽浪 费。
如下图为对齐访问的情况,假设带宽为128B,则只需要加载一次内存
在这里插入图片描述
下面是非对齐的情况,访问的地址是分散的,那么至少需要3次访存
在这里插入图片描述合并访问,如果一个warp中的所有线程访问的是一片连续的内存,那么就会合并访问,反之则反,直接上图。
假设一个线程访问4B的数据,下图warp中所有线程访问的地址是连续的,则为合并访问,没有浪费带宽,总线占用率为100%
在这里插入图片描述

另外一种情况为虽然按线程id顺序访问的内存地址不是有顺序的,但是总的额访问的内存仍然是一块连续的地址,那么总线占用率还是100%

在这里插入图片描述

下面回到我们的矩阵转置中,在实际的矩阵存储中是一维的,如下图所示
在这里插入图片描述

在我们按行读取的时候,属于我们的合并访问,因为访问的地址是连续的,但是我们转置后,按列存储到一维数组的时候时,地址是分散的,则就是非合并访问了,需要访存多次。
或者我们按行去写,这个时候写地址是连续的,是合并访问的,但是读的时候需要按列读,地址不连续,则读不是合并访问了。所以无论我们按行还是按列读都无法避免合并访问。
在这里插入图片描述

那么有没有什么办法能够使得按行读数据,按行写数据呢,那么引入中间一小块内存就能做到,我们知道共享内存在一个block内对于所有线程是可见的,而我们可以申请一小块共享内存,这一块地址是连续的,这个时候,如下图所示,在一个block处理矩阵中一小块元素的时候,我们就能将原本地址不连续的元素,重新排布到在共享内存上面,就变得地址连续了。
在这里插入图片描述
那么我们再将共享内存的数据,按照转置,写入到结果矩阵中,此时读数据按行读,写数据也是按行写,那么读写都是合并访问,而共享内存中地址是连续的,读共享内存的数据也是合并访问,虽然我们每个线程由刚开始读入(行,列)的数据到共享内存中,到从共享内存中读取(列,行)的数据按行输出到矩阵中,访问的元素在逻辑顺序上变了,但是还是处于共享内存的这一片连续的内存中,是不是就是前面说过的这一种合并访问情况
在这里插入图片描述

代码:

__global__ void transposeShared(float *input, float *output, int N)
{
    // 每个线程块都有一个 BLOCK_SIZE ✕ BLOCK_SIZE 的共享内存数组,用于存储其内部处理的数据
	__shared__ int sharedMemory [blocksize] [blocksize];

	// global index	
	int indexX = threadIdx.x + blockIdx.x * blockDim.x;
	int indexY = threadIdx.y + blockIdx.y * blockDim.y;

	// transposed global memory index
	int tindexX = threadIdx.x + blockIdx.y * blockDim.x;
	int tindexY = threadIdx.y + blockIdx.x * blockDim.y;

	// local index
	int localIndexX = threadIdx.x;
	int localIndexY = threadIdx.y;

	int index = indexY * N + indexX;
	int transposedIndex = tindexY * N + tindexX;

	// reading from global memory in coalesed manner and performing tanspose in shared memory
	sharedMemory[localIndexX][localIndexY] = input[index];

	__syncthreads();

	// writing into global memory in coalesed fashion via transposed data in shared memory
	output[transposedIndex] = sharedMemory[localIndexY][localIndexX];
}

时间消耗

cpu
Time taken: 16.800961 ms
普通cuda
Time taken: 0.522752 ms
共享内存cuda
Time taken: 0.395680 ms

汇总代码如下:

#include <iostream>
#include <cuda_runtime.h>
#define blocksize 32
#define BSIZEX 32
#define BSIZEY 32

__global__ void transpose(float *input, float *output, int n)
{

    int row = blockDim.y * blockIdx.y + threadIdx.y;
    int col = blockDim.x * blockIdx.x + threadIdx.x;
    if (row < n && col < n)
    {

        output[col * n + row] = input[row * n + col];
    }
}

__global__ void transposeShared(float *input, float *output, int N)
{
    // 每个线程块都有一个 BLOCK_SIZE ✕ BLOCK_SIZE 的共享内存数组,用于存储其内部处理的数据
	__shared__ int sharedMemory [blocksize] [blocksize];

	// global index	
	int indexX = threadIdx.x + blockIdx.x * blockDim.x;
	int indexY = threadIdx.y + blockIdx.y * blockDim.y;

	// transposed global memory index
	int tindexX = threadIdx.x + blockIdx.y * blockDim.x;
	int tindexY = threadIdx.y + blockIdx.x * blockDim.y;

	// local index
	int localIndexX = threadIdx.x;
	int localIndexY = threadIdx.y;

	int index = indexY * N + indexX;
	int transposedIndex = tindexY * N + tindexX;

	// reading from global memory in coalesed manner and performing tanspose in shared memory
	sharedMemory[localIndexX][localIndexY] = input[index];

	__syncthreads();

	// writing into global memory in coalesed fashion via transposed data in shared memory
	output[transposedIndex] = sharedMemory[localIndexY][localIndexX];
}

// 主机上的矩阵转置函数,用于验证结果
void cpuTranspose(const float *input, float *output, int n)
{
    for (int row = 0; row < n; ++row)
    {
        for (int col = 0; col < n; ++col)
        {
            output[col * n + row] = input[row * n + col];
        }
    }
}

int main()
{
    float *A, *B, *outputcpu;
    int n = 1024;
    size_t size = n * n * sizeof(float);
    A = (float *)malloc(size);
    B = (float *)malloc(size);
    outputcpu = (float *)malloc(size);
    for (int i = 0; i < n * n; i++)
    {
        A[i] = i + 1;
    }

    float *input, *output;
    cudaMalloc((void **)&input, size);
    cudaMalloc((void **)&output, size);

    cudaMemcpy(input, A, size, cudaMemcpyHostToDevice);
    // 定义CUDA事件
    cudaEvent_t start, stop;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);

    dim3 blockDim(BSIZEX, BSIZEY);
    dim3 gridDim((n + BSIZEX - 1) / BSIZEX, (n + BSIZEY - 1) / BSIZEY);

    // 记录开始时间
    cudaEventRecord(start);
    transposeShared<<<gridDim, blockDim>>>(input, output, n);
    // 记录结束时间
    cudaEventRecord(stop);

    // 等待直到事件完成
    cudaEventSynchronize(stop);

    // 计算耗时
    float milliseconds = 0;
    cudaEventElapsedTime(&milliseconds, start, stop);
    printf("Time taken: %f ms\n", milliseconds);

    // 清理事件
    cudaEventDestroy(start);
    cudaEventDestroy(stop);

    cudaMemcpy(B, output, size, cudaMemcpyDeviceToHost);
    cpuTranspose(A, outputcpu, n);

    for (int i = 0; i < n * n; ++i)
    {
        if (fabs(outputcpu[i] - B[i]) > 1e-5)
        {
            printf("result error!");
            break;
        }
    }

    free(A);
    free(B);
    free(outputcpu);
    cudaFree(input);
    cudaFree(output);
    return 0;
}

参考文献:

https://deployment.gitbook.io/love/whitepaper/cuda/global_memory
https://zhuanlan.zhihu.com/p/568769940

相关文章:

  • Bean 的生命周期主要包括以下阶段:
  • CAD Voronoi V3.0.0多图层分区插件
  • PQL查询和监控各类中间件
  • NX二次开发,创建基准平面
  • 【洛谷枚举算法】P2089烤鸡
  • 文件基础IO
  • 笔记五:C语言编译链接
  • [操作系统] ELF文件从形成到加载轮廓
  • 经典核密度估计(Kernel Density Estimation):从直觉到数学
  • DeepSeek:人工智能领域的颠覆者与开拓者
  • deepseek使用记录18——艺术的追问
  • 鸿蒙开发:相对布局RelativeContainer
  • 《实战AI智能体》深度解析Deepseek可以做什么?
  • 概率、泛化与过拟合
  • Python Url地址截取方法
  • 1.4 单元测试与热部署
  • Python——计算机网络
  • vs编译各种报错:未知重写说明符
  • MyBatis 与 JDBC 的关系?
  • 【记录一下】Hierarchical Navigable Small Worlds(HNSW)是什么玩意?
  • 做网站在线支付系统多少钱/杭州seo泽成
  • 霍山做网站/厦门seo大佬
  • 北京医疗网站建设公司/电商数据统计网站
  • 北京网站手机站建设公司电话/凤山网站seo
  • 企业门户网站什么意思/今日新闻摘抄十条简短
  • wordpress搭建的网站能干什么/网络推广运营