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

GPU编程(五): 利用好shared memory

目录

  • 前言
  • CPU矩阵转置
  • GPU实现
  • 简单移植
  • 单block
  • tile
  • 利用率计算
  • shared memory
  • 最后

前言

之前在第三章对比过CPU和GPU, 差距非常大. 这一次来看看GPU自身的优化, 主要是shared memory的用法.


CPU矩阵转置

矩阵转置不是什么复杂的事情. 用CPU实现是很简单的:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

#define LOG_
#define N 1024

/* 转置 */
void transposeCPU( float in[], float out[] )
{
	for ( int j = 0; j < N; j++ )
	{
		for ( int i = 0; i < N; i++ )
		{
			out[j * N + i] = in[i * N + j];
		}
	}
}


/* 打印矩阵 */
void logM( float m[] )
{
	for ( int i = 0; i < N; i++ )
	{
		for ( int j = 0; j < N; j++ )
		{
			printf( "%.1f ", m[i * N + j] );
		}
		printf( "\n" );
	}
}


int main()
{
	int	size	= N * N * sizeof(float);
	float	*in	= (float *) malloc( size );
	float	*out	= (float *) malloc( size );

	/* 矩阵赋值 */
	for ( int i = 0; i < N; ++i )
	{
		for ( int j = 0; j < N; ++j )
		{
			in[i * N + j] = i * N + j;
		}
	}

	struct timeval	start, end;
	double		timeuse;
	int		sum = 0;
	gettimeofday( &start, NULL );

	transposeCPU( in, out );

	gettimeofday( &end, NULL );
	timeuse = end.tv_sec - start.tv_sec + (end.tv_usec - start.tv_usec) / 1000000.0;
	printf( "Use Time: %fs\n", timeuse );

#ifdef LOG
	logM( in );
	printf( "\n" );
	logM( out );
#endif

	free( in );
	free( out );
	return(0);
}
复制代码

GPU实现

简单移植

如果什么都不考虑, 只是把代码移植到GPU:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

#define N 1024
#define LOG_

/* 转置 */
__global__ void transposeSerial( float in[], float out[] )
{
	for ( int j = 0; j < N; j++ )
		for ( int i = 0; i < N; i++ )
			out[j * N + i] = in[i * N + j];
}

/* 打印矩阵 */
void logM( float m[] ){...}

int main()
{
	int size = N * N * sizeof(float);

	float *in, *out;

	cudaMallocManaged( &in, size );
	cudaMallocManaged( &out, size );

	for ( int i = 0; i < N; ++i )
		for ( int j = 0; j < N; ++j )
			in[i * N + j] = i * N + j;

	struct timeval	start, end;
	double		timeuse;
	gettimeofday( &start, NULL );

	transposeSerial << < 1, 1 >> > (in, out);

	cudaDeviceSynchronize();

	gettimeofday( &end, NULL );
	timeuse = end.tv_sec - start.tv_sec + (end.tv_usec - start.tv_usec) / 1000000.0;
	printf( "Use Time: %fs\n", timeuse );


#ifdef LOG
	logM( in );
	printf( "\n" );
	logM( out );
#endif

	cudaFree( in );
	cudaFree( out );
}
复制代码

不用想, 这里肯定是还不如单线程的CPU的, 真的是完完全全的资源浪费. 实测下来, 耗时是CPU的20多倍, 大写的丢人.

单block

单block最多可以开1024线程, 这里就开1024线程跑下.

/* 转置 */
__global__ void transposeParallelPerRow( float in[], float out[] )
{
	int i = threadIdx.x;
	for ( int j = 0; j < N; j++ )
		out[j * N + i] = in[i * N + j];
}

int main()
{
    ...
    transposeParallelPerRow << < 1, N >> > (in, out);
    ...
}
复制代码

效率一下就提升了, 耗时大幅下降.

tile

但是的话, 如果可以利用多个block, 把矩阵切成更多的tile, 效率还会进一步提升.

/* 转置 */
__global__ void transposeParallelPerElement( float in[], float out[] )
{
	int i = blockIdx.x * K + threadIdx.x;
	/* column */
	int j = blockIdx.y * K + threadIdx.y;
	/* row */
	out[j * N + i] = in[i * N + j];
}

int main()
{
	...
	dim3 blocks( N / K, N / K );
	dim3 threads( K, K );

	...
	
	transposeParallelPerElement << < blocks, threads >> > (in, out);
	...
}
复制代码

这些都是GPU的常规操作, 但其实利用率依旧是有限的.


利用率计算

利用率是可以粗略计算的, 比方说, 这里的Memory Clock rateMemory Bus Width是900Mhz和128-bit, 所以峰值就是14.4GB/s.

之前的最短耗时是0.001681s. 数据量是1024*1024*4(Byte)*2(读写). 所以是4.65GB/s. 利用率就是32%. 如果40%算及格, 这个利用率还是不及格的.


shared memory

那该如何提升呢? 问题在于读数据的时候是连着读的, 一个warp读32个数据, 可以同步操作, 但是写的时候就是散开来写的, 有一个很大的步长. 这就导致了效率下降. 所以需要借助shared memory, 由他转置数据, 这样, 写入的时候也是连续高效的了.

/* 转置 */
__global__ void transposeParallelPerElementTiled( float in[], float out[] )
{
	int	in_corner_i	= blockIdx.x * K, in_corner_j = blockIdx.y * K;
	int	out_corner_i	= blockIdx.y * K, out_corner_j = blockIdx.x * K;

	int x = threadIdx.x, y = threadIdx.y;

	__shared__ float tile[K][K];

	tile[y][x] = in[(in_corner_i + x) + (in_corner_j + y) * N];
	__syncthreads();
	out[(out_corner_i + x) + (out_corner_j + y) * N] = tile[x][y];
}

int main()
{

	...
	dim3 blocks( N / K, N / K );
	dim3 threads( K, K );

	struct timeval	start, end;
	double		timeuse;
	gettimeofday( &start, NULL );

	transposeParallelPerElementTiled << < blocks, threads >> > (in, out);
	...

}
复制代码

这样利用率就来到了44%, 及格了.

所以这就是依据架构来设计算法, 回顾一下架构图:


最后

但是44%也就是达到了及格线, 也就是说, 还有更深层次的优化工作需要做. 这些内容也就放在后续文章中了, 有意见或者建议评论区见~


相关文章:

  • Systemd曝3漏洞,大部分Linux将受到***
  • VM虚拟机中fedora28 无法使用中文输入法问题
  • js常用通用函数(++++验证)
  • Spring Boot MyBatis配置多种数据库
  • 简单基于spring的redis配置(单机和集群模式)
  • 关于字符编码你应该知道的事情
  • 微信小程序--------语音识别(前端自己也能玩)
  • IoC组件Unity再续~根据类型字符串动态生产对象
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • 什么是API网关 如何设计亿万级统一网关
  • React的组件模式
  • passportjs 源码分析
  • Google Play 下架 App 之后的替身制作
  • 安卓应用性能调试和优化经验分享
  • Redis 懒删除(lazy free)简史
  • C语言笔记(第一章:C语言编程)
  • HTML中设置input等文本框为不可操作
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • PAT A1017 优先队列
  • python学习笔记 - ThreadLocal
  • Spring Boot MyBatis配置多种数据库
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 每天10道Java面试题,跟我走,offer有!
  • 那些被忽略的 JavaScript 数组方法细节
  • 你不可错过的前端面试题(一)
  • 什么是Javascript函数节流?
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 使用docker-compose进行多节点部署
  • 详解移动APP与web APP的区别
  • 延迟脚本的方式
  • 一个JAVA程序员成长之路分享
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • #define
  • (1)(1.9) MSP (version 4.2)
  • (1)STL算法之遍历容器
  • (42)STM32——LCD显示屏实验笔记
  • (poj1.2.1)1970(筛选法模拟)
  • (二)fiber的基本认识
  • (南京观海微电子)——I3C协议介绍
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • (转载)利用webkit抓取动态网页和链接
  • *** 2003
  • ******之网络***——物理***
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .net core 6 集成和使用 mongodb
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .NET 反射的使用
  • .NET 设计一套高性能的弱事件机制
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .Net多线程总结
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • @Autowired @Resource @Qualifier的区别
  • @Autowired自动装配