cuda中如何选择block尺寸

cuda中如何选择block尺寸,第1张

6416=1024

线程,此时

SM

上还有

1024

个线程空闲,显然这种设置

无法有效利用

GPU

l

如果设置

block

尺寸为

1616=256

根据

Threads Per Multiprocessor

的限

制每个

SM

可容纳

2048/256=8

block

,小于

Thread

Blocks

Per

Multiprocessor

的限制;此时

SM

每次运行

8

block

2048

个线程,

能够占满整个

SM

l

如果设置

block

尺寸为

3232=1024

,根据

Threads Per Multiprocessor

限制每个

SM

可运行

2048/1024=2

block

,小于

Thread

Blocks

Per

Multiprocessor

的限制;此时

SM

每次运行

2

block

2048

个线程,

能够占满整个

SM

注意事项

A

:根据

Max Thread Block Size

的限制,

block

中线程个数上限为

1024

注意事项

B

CUDA

中,

线程调度单位称作

Warp

根据

Threads Per Warp

这个规格,每次以

32

个线程为单位进行调度,因此

Block

中的

Thread

数目应当

32

的倍数。

注意事项

C

Block

如果是多维的,每个维度也都有大小限制,图

5

中的

Threads

Dimensions

Block

Dimension

Limit

1024102464

,也就是

Block

的第一维不能超过

1024

,第二维不能超过

1024

第三位不能超过

64

综上所述,我们的初步设计结果是:在无需考虑向下兼容的情况下,对

GTX650

显卡将

block

尺寸设置为

3232=1024

应该是比较恰当的(哦,忘了说,

我主要用

CUDA

做图像处理,因此

block

尺寸都习惯设为二维)

2

SM

资源对

Block

尺寸设计的影响

制约

block

尺寸分配的还有其他资源的限制,例如

shared

memory

register

等。在每一个

SM

上这些资源都是有限的,如果所有线程要求的资源总和过多,

CUDA

只能通过强制减少

Block

数来保证资源供应。

顺便提一下,

Shared Memory

Register

都位于

GPU

片上(相对的,

Global

Memory

Local Memory

位于显存)

速度超快的,

想要

CUDA

程序跑得快,

Shared Memory

Register

的细心设计是必不可少的。

21

Register

Block

尺寸设计的影响

根据

Register File Size

的限制(

CUDA-Z

描述为

Regs Per Block

,不准确,

应该是

Regs Per Multiprocessor

,每个

SM

上只能供应

65536

Register

根据

Max Registers Per Thread

的限制,

每个线程不能使用超过

63

Register

计算示例:

l

假设

Block

尺寸设计为

3232=1024

每个

Thread

需要使用

32

Register

则一个

SM

上能承担的

Thread

数量为

65536/32=2048

,刚好可以满足需

求。

l

Block

尺寸同上,每个

Thread

需要使用

33

Register

,则一个

SM

上能

承担的

Thread

数量为

65536/33

198594

。但是,前面说过,如果请求

资源过多,

CUDA

将会通过强制减少

Block

(而不是

Thread

数)

来保

证资源供应。本示例中的情况可以满足

1

Block

1024

Thread

)的

需求,不能满足

2

Block

2048

Thread

)的需求,因此实际只有

1

Block

1024

个线程在运行。和上个示例相对比,因为多请求了

1

Register

,就灭掉了其他

1024

Thread

的生存机会,不划算啊。

22

Shared Memory

Block

尺寸设计的影响

21

基本类似。

根据

Max

Shared

Memory

Per

Multiprocessor

的限制,每个

SM

上只能供应

49152 Byte

Shared Memory

首先需要明确一点:

Shared Memory

是分配给

Block

而不是

Thread

的,

被每

Block

内的所有

Thread

共享(所以才叫做

”Shared”

Block

中的

Thread

能够

合作运行也是基于这一点。

计算示例:

l

假设每个

Block

使用了

20000 Byte

Shared Memory

,则一个

SM

上能

承担的

Block

数量为

49152/20000=24576

,可以满足需求。

l

假设每个

Block

使用了

30000 Byte

Shared Memory

,则一个

SM

上能

承担的

Block

数量为

49152/30000=16384

。本示例中的情况只能满足

1

Block

的需求,因此实际只有

1

Block

在运行。如果这里

Block

尺寸为

3232=1024

,则

SM

中另外

1024

个线程容量都被浪费了。

23

占用率计算器的使用

上面的计算乍一看比较复杂,所幸

nVidia

已经提供了一个很好的计算工具,

这就是刚才提到

CUDA_Occupancy_Calculatorxls

,如图

8

所示。

这个工具的使用非常简单,

只要遵循

3

个步骤即可。

1)

2)

是需要用户使用

下拉菜单进行选择的项目,

3)

是占用率计算结果。下面我们分别进行介绍。

1)

是选择

GPU

的计算能力,

前面说过可以使用

CUDA-Z

软件查询,

GTX650

显卡选择

30

1b)

在表

1

中已经给出了,数值为

49152

2)

中的

Threads Per Block

就设为前面计算得到的

3232=1024

那么一个程序使用的

Register

Shared Memory

如何得到呢?这时可以使用

--ptxas-options=-v

编译指令。

S

U

M

E

C

O

L

L

E

C

T

I

O

N

8 CUDA GPU

占用率计算器

VS2008

中可以如下 *** 作:

a

打开

Project

属性;

9

b

将属性中

CUDA

Runtime

API\GPU

中的

Verbose

PTXAS

Output

设置为

Yes

10

c

重新编译程序后,即可在

Output

窗口中看到类似下面的信息

ptxas info

: Compiling entry function '_Z8my_kernelPf' for 'sm_10'

ptxas info

: Used 5 registers, 8+16 bytes smem

可以看出本程序使用了

5

Register

8+16

Byte

Shared Memory

但是如果程序在运行时还定义了

2048 Byte

external shared memory array

则总的

Shared

Memory

占用应当是

2048+8+16=2072

。将

Register

Shared

Memory

信息填入图

5

中的

2)

后即可看到计算器的计算结果,

如图

11

所示。

11

3)

中显示的就是资源占用情况,可见

SM

的占用率是

100%

,没有计算能力

被浪费,说明这种配置是合理的。

S

U

M

E

C

O

L

L

E

C

T

I

O

N

至此,

Block

尺寸的设计基本完成。

3

Block

尺寸的合理性

资源(

Register

Shared Memory

)是稀缺的,一定要分配给最重要的语句。

虽然

Thread

越多就越能隐藏访问延迟,但同时每个

Thread

能够使用的资源也就

相对减少了,如何在这两者之间找到平衡,只有程序实际运行时才能得到检验。

现实和理论总是有差别,

但如果能够把握基本的原理,

肯定不会在通往最终目标

的路上偏离太多。

本文属于多线程系列:

多线程探索一-概念

多线程探索二-GCD

多线程探索三-NSOperation

多线程探索四-锁

NSOperation是APPLE推出的基于 GCD 封装的一套面向对象的API,接口更加简洁,上手更加方便。

优点

NSOperation是一个抽象类,无法直接使用。系统提供了两个子类的实现,可以直接上手,当然也可以自己继承定制operation。

非并发的operation,通过 target selector添加任务

并发的operation,通过添加block添加并发任务,可以在一个opertation中添加多个block并发执行。

当所有的block都执行完成后,operation会自动finish

测试发现

+ blockOperationWithBlock: 添加的任务 一般在当前线程执行

- addExecutionBlock: 有开启新线程的能力

自定义operation, 抽象类提供了几个方法 官方文档 其实超级详细

非并发的operation

main 一般推荐把task内容放在这里, 如果需要访问operation里的数据,记得保证线程安全

并发的operation 至少需要重写以下几个方法

finished这个状态在 *** 作完成后请及时设置为YES,因为NSOperationQueue所管理的队列中,只有isFinished为YES时才将其移除队列,这点在内存管理和避免死锁很关键。

下面是个demo

NSoperationQueue 和 NSOperation配合使用

NSoperationQueue内的对象是线程安全的

NSoperationQueue同时支持KVC和KVO

NSOperation 可以设置Dependency 但是 dependency的设置需要在operation 添加到 operation queue 之前才能生效。dependency可以跨queue。 当queue的maxConcurrentOperationCount == 1 的时候 无效。

NSOperationQueuePriority 默认是normal,当有需要时并且在没有设置dependency的情况下使用。 (不过本人尝试后发现并没有什么效果 ~~)

此方法是iOS13以后添加的,用法类似dispatch_barrier,详情可以参考上篇 多线程探索二-GCD ,可以用于在queue里前面添加的task执行后做一些同一个处理。

IAsyncResult 异步设计模式通过名为 BeginOperationName 和 EndOperationName 的两个方法来实现原同步方法的异步调用 如 FileStream 类提供了 BeginRead 和 EndRead 方法来从文件异步读取字节 它们是 Read 方法的异步版本

Begin 方法包含同步方法签名中的任何参数 此外还包含另外两个参数 一个AsyncCallback 委托和一个用户定义的状态对象 委托用来调用回调方法 状态对象是用来向回调方法传递状态信息 该方法返回一个实现 IAsyncResult 接口的对象

End 方法用于结束异步 *** 作并返回结果 因此包含同步方法签名中的 ref 和 out 参数 返回值类型也与同步方法相同 该方法还包括一个 IAsyncResult 参数 用于获取异步 *** 作是否完成的信息 当然在使用时就必须传入对应的 Begin 方法返回的对象实例

开始异步 *** 作后如果要阻止应用程序 可以直接调用 End 方法 这会阻止应用程序直到异步 *** 作完成后再继续执行 也可以使用 IAsyncResult 的 AsyncWaitHandle 属性 调用其中的WaitOne等方法来阻塞线程 这两种方法的区别不大 只是前者必须一直等待而后者可以设置等待超时

如果不阻止应用程序 则可以通过轮循 IAsyncResult 的 IsCompleted 状态来判断 *** 作是否完成 或使用 AsyncCallback 委托来结束异步 *** 作 AsyncCallback 委托包含一个 IAsyncResult 的签名 回调方法内部再调用 End 方法来获取 *** 作执行结果

代码

C#异步编程模式IAsyncResult之IAsyncResult 接口

public interface IAsyncResult

{

object AsyncState { get; }

WaitHandle AsyncWaitHandle { get; }

bool CompletedSynchronously { get; }

bool IsCompleted { get; }

}

我用一个 AsyncDemo 类作为异步方法的提供者 后面的程序都会调用它 内部很简单 构造函数接收一个字符串作为 name Run 方法输出 My name is + name 而异步方法直接用委托的 BeginInvoke 和 EndInvoke 方法实现

public class AsyncDemo

{

// Use in asynchronous methods

private delegate string runDelegate();

private string m_Name;

private runDelegate m_Delegate;

public AsyncDemo(string name)

{

m_Name = name;

m_Delegate = new runDelegate(Run);

}

///// ﹤summary﹥

/// Synchronous method

/// ﹤/summary﹥

/// ﹤returns﹥﹤/returns﹥

public string Run()

{

return My name is + m_Name;

}

///// ﹤summary﹥

/// Asynchronous begin method

/// ﹤/summary﹥

/// ﹤param name= callBack ﹥﹤/param﹥

/// ﹤param name= stateObject ﹥﹤/param﹥

/// ﹤returns﹥﹤/returns﹥

public IAsyncResult BeginRun(

AsyncCallback callBack Object stateObject)

{

try

{

return m_Delegate BeginInvoke(callBack stateObject);

}

catch(Exception e)

{

// Hide inside method invoking stack

throw e;

}

}

///// ﹤summary﹥

/// Asynchronous end method

/// ﹤/summary﹥

/// ﹤param name= ar ﹥﹤/param﹥

/// ﹤returns﹥﹤/returns﹥

public string EndRun(IAsyncResult ar)

{

if (ar == null)

throw new NullReferenceException(

Arggument ar can t be null );

try

{

return m_Delegate EndInvoke(ar);

}

catch (Exception e)

{

// Hide inside method invoking stack

throw e;

}

}

}

C#异步编程模式IAsyncResult *** 作步骤 首先是 Begin 之后直接调用 End 方法 当然中间也可以做其他的 *** 作

class AsyncTest

{

static void Main(string[] args)

{

AsyncDemo demo = new AsyncDemo( jiangnii );

// Execute begin method

IAsyncResult ar = demo BeginRun(null null);

// You can do other things here

// Use end method to block thread

// until the operation is plete

string demoName = demo EndRun(ar);

Console WriteLine(demoName);

}

}

也可以用 IAsyncResult 的 AsyncWaitHandle 属性 我在这里设置为 秒超时

class AsyncTest

{

static void Main(string[] args)

{

AsyncDemo demo = new AsyncDemo( jiangnii );

// Execute begin method

IAsyncResult ar = demo BeginRun(null null);

// You can do other things here

// Use AsyncWaitHandle WaitOne method to block thread for second at most

ar AsyncWaitHandle WaitOne( false);

if (ar IsCompleted)

{

// Still need use end method to get result

// but this time it will return immediately

string demoName = demo EndRun(ar);

Console WriteLine(demoName);

}

else

{

Console WriteLine( Sorry

can t get demoName the time is over );

}

}

}

C#异步编程模式IAsyncResult要注意的还有 不中断的循环 每次循环输出一个

class AsyncTest

{

static void Main(string[] args)

{

AsyncDemo demo = new AsyncDemo( jiangnii );

// Execute begin method

IAsyncResult ar = demo BeginRun(null null);

Console Write( Waiting );

while (!ar IsCompleted)

{

Console Write( );

// You can do other things here

}

Console WriteLine();

// Still need use end method to get result

//but this time it will return immediately

string demoName = demo EndRun(ar);

Console WriteLine(demoName);

}

}

最后是使用回调方法并加上状态对象 状态对象被作为 IAsyncResult 参数的 AsyncState 属性被传给回调方法 回调方法执行前不能让主线程退出 我这里只是简单的让其休眠了 秒 另一个与之前不同的地方是 AsyncDemo 对象被定义成了类的静态字段 以便回调方法使用

class AsyncTest

{

static AsyncDemo demo = new AsyncDemo( jiangnii );

static void Main(string[] args)

{

// State object

bool state = false;

// Execute begin method

IAsyncResult ar = demo BeginRun(

new AsyncCallback(outPut) state);

// You can do other thins here

// Wait until callback finished

System Threading Thread Sleep( );

}

// Callback method

static void outPut(IAsyncResult ar)

{

bool state = (bool)ar AsyncState;

string demoName = demo EndRun(ar);

if (state)

{

Console WriteLine(demoName);

}

else

{

Console WriteLine(demoName + isn t it );

}

}

}

C#异步编程模式IAsyncResult的后话

对于一个已经实现了 BeginOperationName 和 EndOperationName方法的对象 我们可以直接用上述方式调用 但对于只有同步方法的对象 我们要对其进行异步调用也不需要增加对应的异步方法 而只需定义一个委托并使用其 BeginInvoke 和 EndInvoke 方法就可以了

lishixinzhi/Article/program/net/201311/11864

以上就是关于cuda中如何选择block尺寸全部的内容,包括:cuda中如何选择block尺寸、线程探索三-NSOperation、C#异步编程模式IAsyncResult概述等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9672351.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-30
下一篇2023-04-30

发表评论

登录后才能评论

评论列表(0条)

    保存