CUDA Pro:通过向量化内存访问提高性能

许多CUDA内核受带宽限制,而新硬件中触发器与带宽的比率不断提高,导致带宽受限制的内核更多。这使得采取措施减轻代码中的带宽瓶颈非常重要。本文将展示如何在CUDA C / C ++中使用向量加载和存储,以帮助提高带宽利用率,同时减少已执行指令的数量。

从以下简单的内存复制内核开始。

__global__ void device_copy_scalar_kernel(int* d_in, int* d_out, int N) {

int idx = blockIdx.x * blockDim.x + threadIdx.x;

for (int i = idx; i < N; i += blockDim.x * gridDim.x) {

d_out[i] = d_in[i];

}

}

void device_copy_scalar(int* d_in, int* d_out, int N)

{

int threads = 128;

int blocks = min((N + threads-1) / threads, MAX_BLOCKS);

device_copy_scalar_kernel<<<blocks, threads>>>(d_in, d_out, N);

}

代码使用的是网格跨度循环。图1显示了内核吞吐量(GB / s)与副本大小的关系。

图1:复制带宽与复制大小的关系。

可以使用CUDA Toolkit 附带的cuobjdump工具检查该内核的程序集。

%> cuobjdump -sass可执行文件

标量复制内核主体的SASS如下:

/ * 0058 * / IMAD R6.CC,R0,R9,c [0x0] [0x140]

/ * 0060 * / IMAD.HI.X R7,R0,R9,c [0x0] [0x144]

/ * 0068 * / IMAD R4.CC,R0,R9,c [0x0] [0x148]

/ * 0070 * / LD.E R2,[R6]

/ * 0078 * / IMAD.HI.X R5,R0,R9,c [0x0] [0x14c]

/ * 0090 * / ST.E [R4],R2

可以看到总共六个与复制操作相关的指令。四个IMAD指令计算加载和存储地址和LD.E与ST.E负载位和32位来自这些地址存储。

可以使用向量化的加载和存储指令LD.E.{64,128}和来提高此操作的性能ST.E.{64,128}。这些操作也可以加载和存储数据,但可以64位或128位宽度进行加载和存储。使用矢量化负载减少了指令总数,减少了等待时间,并提高了带宽利用率。

使用矢量载荷的最简单的方法是使用在CUDA C / C ++标准头中定义的向量的数据类型,如int2,int4,或 float2。可以通过C / C ++中的类型转换轻松地使用这些类型。例如,在C ++可以重铸int指针d_in到一个int2使用指针reinterpret_cast<int2*>(d_in)。在C99中,可以使用强制转换运算符做相同的事情:(int2*(d_in))。

取消引用那些指针将导致编译器生成矢量化指令。但是,有一个重要警告:这些指令需要对齐的数据。设备分配的内存会自动对齐到数据类型大小的倍数,但是如果偏移指针,则偏移也必须对齐。例如reinterpret_cast<int2*>(d_in+1),无效是因为d_in+1未与对齐sizeof(int2)。

如果使用“对齐”偏移量,则可以安全地偏移数组,如 reinterpret_cast<int2*>(d_in+2)中所示。也可以使用结构生成矢量化载荷,只要该结构的大小为2个字节即可。

struct Foo {int a,int b,double c}; // 16个字节

Foo * x,* y;

…

x [i] = y [i];

既然已经看到了如何生成向量化指令,那么让修改内存复制内核以使用向量加载。

__global__ void device_copy_vector2_kernel(int* d_in, int* d_out, int N) {

int idx = blockIdx.x * blockDim.x + threadIdx.x;

for (int i = idx; i < N/2; i += blockDim.x * gridDim.x) {

reinterpret_cast<int2*>(d_out)[i] = reinterpret_cast<int2*>(d_in)[i];

}

// in only one thread, process final element (if there is one)

if (idx==N/2 && N%2==1)

d_out[N-1] = d_in[N-1];

}

void device_copy_vector2(int* d_in, int* d_out, int n) {

threads = 128;

blocks = min((N/2 + threads-1) / threads, MAX_BLOCKS);

device_copy_vector2_kernel<<<blocks, threads>>>(d_in, d_out, N);

}

该内核只有几处更改。首先,循环现在仅执行N/ 2次,因为每次迭代处理两个元素。其次,在副本中使用上述技术。第三,处理所有可能N被2整除的剩余元素。最后,启动的线程数量是标量内核中数量的一半。

检查SASS,看到以下内容。

/ * 0088 * / IMAD R10.CC,R3,R5,c [0x0] [0x140]

/ * 0090 * / IMAD.HI.X R11,R3,R5,c [0x0] [0x144]

/ * 0098 * / IMAD R8.CC,R3,R5,c [0x0] [0x148]

/ * 00a0 * / LD.E.64 R6,[R10]

/ * 00a8 * / IMAD.HI.X R9,R3,R5,c [0x0] [0x14c]

/ * 00c8 * / ST.E.64 [R8],R6

编译器生成LD.E.64和ST.E.64。其他所有指令均相同。由于循环仅执行N / 2次,因此将执行一半的指令。在指令绑定或延迟绑定的内核中,指令数量的2倍改进非常重要。

还可以编写复制内核的vector4版本。

___global__ void device_copy_vector4_kernel(int* d_in, int* d_out, int N) {

int idx = blockIdx.x * blockDim.x + threadIdx.x;

for(int i = idx; i < N/4; i += blockDim.x * gridDim.x) {

reinterpret_cast<int4*>(d_out)[i] = reinterpret_cast<int4*>(d_in)[i];

}

// in only one thread, process final elements (if there are any)

int remainder = N%4;

if (idx==N/4 && remainder!=0) {

while(remainder) {

int idx = N - remainder--;

d_out[idx] = d_in[idx];

}

}

}

void device_copy_vector4(int* d_in, int* d_out, int N) {

int threads = 128;

int blocks = min((N/4 + threads-1) / threads, MAX_BLOCKS);

device_copy_vector4_kernel<<<blocks, threads>>>(d_in, d_out, N);

}

相应的SASS是:

/*0090*/                IMAD R10.CC, R3, R13, c[0x0][0x140]

/*0098*/                IMAD.HI.X R11, R3, R13, c[0x0][0x144]

/*00a0*/                IMAD R8.CC, R3, R13, c[0x0][0x148]

/*00a8*/                LD.E.128 R4, [R10]

/*00b0*/                IMAD.HI.X R9, R3, R13, c[0x0][0x14c]

/*00d0*/                ST.E.128 [R8], R4

在这里可以看到生成的LD.E.128和ST.E.128。此版本的代码将指令数减少了4倍。可以在图2中看到所有3个内核的整体性能。

图2:矢量化内核的复制带宽与复制大小的关系。

在几乎所有情况下,矢量化载荷都优于标量载荷。但是请注意,使用矢量化负载会增加寄存器压力并降低总体并行度。因此,如果的内核已经受到寄存器限制或并行度很低,则可能需要坚持标量加载。同样,如前所述,如果指针未对齐或以字节为单位的数据类型大小不是2的幂,则不能使用矢量化加载。

向量化加载是应该尽可能使用的基本CUDA优化,因为它们会增加带宽,减少指令数量并减少延迟。本文展示了如何通过较少的更改就可以轻松地将向量化负载合并到现有内核中。

CUDA Pro:通过向量化内存访问提高性能的更多相关文章

  1. 深度解读Facebook刚开源的beringei时序数据库——数据压缩delta of delta+充分利用内存以提高性能

    转自:https://yq.aliyun.com/topic/58?spm=5176.100239.blogcont69354.9.MLtp4T 摘要: Facebook最近开源了beringei时序 ...

  2. C# 7.2 通过 in 和 readonly struct 减少方法值复制提高性能

    在 C# 7.2 提供了一系列的方法用于方法参数传输的时候减少对结构体的复制从而可以高效使用内存同时提高性能 在开始阅读之前,希望读者对 C# 的值类型.引用类型有比较深刻的认知. 在 C# 中,如果 ...

  3. 2018-12-25-C#-7.2-通过-in-和-readonly-struct-减少方法值复制提高性能

    title author date CreateTime categories C# 7.2 通过 in 和 readonly struct 减少方法值复制提高性能 lindexi 2018-12-2 ...

  4. Integer 如何实现节约内存和提升性能的?

    在Java5中,为Integer的操作引入了一个新的特性,用来节省内存和提高性能.整型对象在内部实现中通过使用相同的对象引用实现了缓存和重用. 上面的规则默认适用于整数区间 -128 到 +127(这 ...

  5. 【CUDA 基础】5.4 合并的全局内存访问

    title: [CUDA 基础]5.4 合并的全局内存访问 categories: - CUDA - Freshman tags: - 合并 - 转置 toc: true date: 2018-06- ...

  6. 【CUDA 基础】4.3 内存访问模式

    title: [CUDA 基础]4.3 内存访问模式 categories: - CUDA - Freshman tags: - 内存访问模式 - 对齐 - 合并 - 缓存 - 结构体数组 - 数组结 ...

  7. php大型网站如何提高性能和并发访问

    一.大型网站性能提高策略: 大型网站,比如门户网站,在面对大量用户访问.高并发请求方面,基本的解决方案集中在这样几个环节:使用高性能的服务器.高性能的数据库.高效率的编程语言.还有高性能的Web容器. ...

  8. 【CUDA 基础】5.3 减少全局内存访问

    title: [CUDA 基础]5.3 减少全局内存访问 categories: - CUDA - Freshman tags: - 共享内存 - 归约 toc: true date: 2018-06 ...

  9. 利用Linux文件系统内存cache来提高性能

    https://www.linuxjournal.com/article/6345 利用Linux文件系统内存cache来提高性能 本地磁盘文件->socket发送,4步骤数据流向: hard ...

随机推荐

  1. 解决小程序中Data.parse()获取时间戳IOS不兼容

    由于与后台接口必须对比时间戳所以首先得前台获取时间戳.刚开始是获取手机本地时间,但用户改了时间就废了..... 后来就从服务器上获取个时间再转换为时间戳(是不是很操蛋,先从服务器上获取在TM的自己比较 ...

  2. hdu3986 spfa + 枚举最短路上的边

    题意: 删除一条边后,求最短路中最长的那个(敌人搞破坏). 思路: 如果你是敌人你肯定删除最短路上的边,删除别的边最短路的值是不会变的,所以直接枚举最短路上的边去删除,取得最大的就行了... #inc ...

  3. 从苏宁电器到卡巴斯基第14篇:我在苏宁电器当营业员 VI

    我也过了一把讲师的瘾 由于iPhone已经成为了我们的主推产品,因此苏宁要求手机专区的每一个人,不论是自营还是厂促,都要对iPhone非常了解才可以.于是,督导也没有事先通知我,就直接让我给手机专区的 ...

  4. IDA动态调试Android的DEX文件

    Android程序的dex文件的动态调试确实是个大问题,网上也有一些教程但是不是特别的详细,今天用到了IDA动态调试Android的DEX文件,特此记录一下. IDA 6.6新添加了对dex文件的调试 ...

  5. hdu4720 三角形的外接圆

    题意:       给你四个点,问你第四个点是否在前三个点围成的三角形的外接圆上. 思路:       水题,就是练练用魔板罢了,当该三角形是锐角三角形的时候,圆心是任意两条边中垂线的交点,半径是圆心 ...

  6. MS06-040漏洞研究(上)【转载】

    课程简介 我在之前的课程中讨论过W32Dasm这款软件中的漏洞分析与利用的方法,由于使用该软件的人群毕竟是小众群体,因此该漏洞的危害相对来说还是比较小的.但是如果漏洞出现在Windows系统中,那么情 ...

  7. CVE-2018-0802:Microsoft office 公式编辑器 font name 字段二次溢出漏洞调试分析

    \x01 前言 CVE-2018-0802 是继 CVE-2017-11882 发现的又一个关于 font name 字段的溢出漏洞,又称之为 "第二代噩梦公式",巧合的是两个漏洞 ...

  8. Windows PE 第一章 熟悉OD(顺便破解一个小工具)

    熟悉OD(顺便破解一个小工具) 上一节了解了OD的简单使用,这次就练习下,目标是破解一款小软件(入门练手用的,没有壳什么的). 首先我们来看一下这个小软件: 我们的目的是输入任何字符串都可以成功注册, ...

  9. node-多进程

    Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能. 每个子进程总是带有三个流对象: child.stdin, ...

  10. node-OS&Domain&Net&Path

    OS--------------------------------------------- Node.js os 模块提供了一些基本的系统操作函数. var os = require(" ...