
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概述等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)