我们关于将执行资源分配给区块的讨论提出了一个重要问题。我们如何确定可用资源的数量?当CUDA应用程序在系统上执行时,它如何确定设备中的SM数量以及可以分配给每个SM的块和线程数量?可能与执行CUDA应用程序相关的其他资源尚未讨论。一般来说,许多现代应用程序被设计为在各种硬件系统上执行。该应用程序通常需要查询底层硬件的可用资源和功能,以便利用能力更强的系统,同时补偿能力较差的系统。
在CUDA C中,存在主机代码的内置机制来查询系统中可用设备的属性。CUDA运行时系统(设备驱动程序)有一个API函数cudaGetDeviceCount,该函数返回系统中可用的CUDA设备数量。主机代码可以使用以下语句确定可用CUDA设备的数量:
int dev_count;
cudaGetDeviceCount(&dev_count);
资源和能力查询
在日常生活中,我们经常查询环境中可用的资源和能力。当我们预订酒店时,我们可以查看酒店房间附带的设施。如果房间里有吹风机,我们不需要带吹风机。大多数美国酒店客房都配有吹风机;其他地区的许多酒店没有。
一些亚洲和欧洲酒店提供牙膏甚至牙刷,而大多数美国酒店则不提供。许多美国酒店同时提供洗发水和护发素,而其他大洲的酒店通常只提供洗发水。
如果房间里有微波炉和冰箱,我们可以拿走晚餐的剩菜,预计第二天再吃。如果酒店有游泳池,我们可以带上泳衣,并在商务会议后畅游。如果酒店没有游泳池,但有健身室,我们可以带跑鞋和运动服。一些高端的亚洲酒店甚至提供运动服!
这些酒店设施是酒店财产或资源和能力的一部分。资深旅行者在酒店网站上查看这些酒店,选择更符合他们需求的酒店,并根据这些细节更高效地打包。
虽然可能并不明显,但现代PC系统通常有两个或多个CUDA设备。原因是许多PC系统都配有一个或多个“集成”GPU。这些GPU是默认的图形单元,提供基本的功能和硬件资源,为现代基于Windows的用户界面执行最小的图形功能。大多数CUDA应用程序在这些集成设备上表现不佳。这一弱点将是主机代码遍及所有可用设备,查询其资源和功能,并选择具有足够资源的设备来令人满意地执行应用程序的原因。
CUDA运行时从0到dev_count-1对系统中所有可用设备进行编号。它提供了一个APl函数cudaGetDeviceProperties,该函数返回其编号作为参数给出的设备的属性。我们可以使用主机代码中的以下语句来遍默化可用设备并查询其属性:
内置类型cudaDeviceProp是C结构类型,其字段表示CUDA设备的属性。读者参考该类型所有领域的CUDA C编程指南。我们将讨论其中一些与将执行资源分配给线程特别相关的领域。我们假设属性在dev_prop变量中返回,其字段由cudaGet- DeviceProperties函数设置。如果读者选择以不同的方式命名变量,则显然需要在下面的讨论中替换适当的变量名称。
顾名思义,dev_prop.maxThreadsPerBlock字段表示查询设备中块中允许的最大线程数。一些设备在每个块中允许多达1024个线程,而其他设备允许更少的线程。未来的设备甚至可能每个块允许超过1024个线程。因此,应该查询可用的设备,并确定每个块中允许足够数量线程的设备。
设备中的SM数量在dev_prop.multiProcessorCount中给出。正如我们之前所讨论的,一些设备只有少量的SM(例如tWO),有些设备的SM数量要多得多(例如30)。如果应用程序需要大量的SM才能达到令人满意的性能,它绝对应该检查潜在设备的此属性。此外,设备的时钟频率在dev_prop.clockRate中。时钟速率和SM数量的组合很好地表明了设备的硬件执行能力。
主机代码可以在字段dev_prop.maxThreadsDim[0]、dev_prop.maxThreads
Dim[1]和dev_prop.maxThreadsDim[2](用于x、y和z维度)中找到块每个维度允许的最大线程数。此类信息可用于自动调谐系统,以便在评估底层硬件性能最佳的块尺寸时设置块尺寸的范围。同样,它可以确定dev_prop.maxGridSize[0],、dev_prop.maxGridSize[1]和dev_prop.maxGridSize[2](用于x、y和z维度)中网格每个维度允许的最大块数。这些信息通常用于确定网格是否有足够的线程来处理整个数据集,或者是否需要一些迭代。
cudaDeviceProp类型还有更多字段。我们将在介绍它们旨在反映的概念和功能时讨论它们。