C++ 在CUDA中的表现
我似乎无法找出影响内核性能的潜在因素。我实现了两个简单的内核,一个加载两个图像并将其逐像素相加,另一个加载两个图像并对其进行逐位运算。现在,我已经对它们进行了模板化,以便内核可以拍摄8位和32位图像,以及1、3和4通道图像 因此,最初我让两个内核都以C++ 在CUDA中的表现,c++,performance,cuda,C++,Performance,Cuda,我似乎无法找出影响内核性能的潜在因素。我实现了两个简单的内核,一个加载两个图像并将其逐像素相加,另一个加载两个图像并对其进行逐位运算。现在,我已经对它们进行了模板化,以便内核可以拍摄8位和32位图像,以及1、3和4通道图像 因此,最初我让两个内核都以uchar3和float3的形式加载全局内存,以及uchar4等。但是,由于合并,我不太确定是否要使用三元组,所以我想我应该对其进行一次分析。我认为,由于操作与通道编号无关,因此我可以像读取三倍宽度的单通道uchar图像一样读取图像,而不是真正的uc
uchar3
和float3
的形式加载全局内存,以及uchar4
等。但是,由于合并,我不太确定是否要使用三元组,所以我想我应该对其进行一次分析。我认为,由于操作与通道编号无关,因此我可以像读取三倍宽度的单通道uchar
图像一样读取图像,而不是真正的uchar3
图像
事实上,uchar3
全局加载比uchar
加载慢得多。我的努力得到了证实。但是,唉,这只发生在算术内核上。按位AND运算显示了完全相反的结果
现在,我知道我可以将图像数据加载为uint
s,而不是uchar
s,用于按位操作,这应该可以完美地处理合并。但让我们假设我只是想学习和理解正在发生的事情
让我们忘记float3
s和float4
s等。我的问题是uchar
版本的内核。那么,简而言之,为什么uchar
加载有时比uchar3
加载快,有时又不快
我使用的是GTX 470,计算能力2.0
另外,根据CUDA编程指南,逻辑操作和add操作具有相同的吞吐量。(我的内核实际上必须首先将uchar
s转换为uint
s,但这应该发生在两个内核中。)因此,从我收集的数据来看,执行长度应该大致相同
算术添加内核(uchar
version):
和内核类似。(老实说,我不确定我是否准确地记得内核……我明天会确认)。
uchar3
由于SM的指令集中没有24位加载,因此编译器将加载拆分为单独的加载。因此,它们从未合并。在某种程度上,缓存将缓解这一问题
但是,根据具体的执行配置,每个线程可能只有大约10.7字节的缓存(您的示例可能接近该值,因为内核很简单,所以很多线程可以在一个SM上并发运行)。由于缓存不是完全关联的,在抖动发生之前,每个线程的可用字节数可能会小得多。具体发生的时间取决于许多因素,包括指令的精确调度,即使对于具有相同记录吞吐量的指令,也可能有所不同
您可以比较两个版本的cuobjdump-sass
可执行文件的输出,以查看编译器的静态调度是否相同。然而,运行时动态调度的工作原理基本上是不可观察的
正如您所注意到的,图像的所有通道都以相同的方式进行处理,因此在线程之间如何分配它们并不重要。您最好的选择是使用
uchar4
而不是uchar3
或uchar
,这(假设图像适当对齐)将提供独立于缓存的联合访问。这将导致更短和更一致的执行时间。您能给我们看看您的内核吗?对于未知的代码很难推理。也许可以告诉我们执行时间的比较情况(也就是说,内核在一个版本或另一个版本中所花费的时间是否相似,哪个版本更快,…),你能给出一个简洁的问题吗?您想知道为什么加载一堆uchar
比加载一堆uchar3
更快?CUDA 5中的探查器将发出通知,如果未平衡的加载/存储是一个问题,即使对于最基本类型的探查运行也是如此。关于这两种情况,它在百分比上说了什么?负载是线性的,并且完美地结合在一起,至少在uchar的情况下是这样。我现在不在上班,所以无法粘贴内核。我想知道为什么它有时快,有时不快。
__global__ void add_8uc1(uchar* inputOne, uchar* inputTwo, uchar* output, unsigned int width, unsigned int height, unsigned int widthStep)
{
const int xCoordinateBase = blockIdx.x * IMAGE_X * IMAGE_MULTIPLIER + threadIdx.x;
const int yCoordinate = blockIdx.y * IMAGE_Y + threadIdx.y;
if (yCoordinate >= height)
return;
#pragma unroll IMAGE_MULTIPLIER
for (int i = 0; i < IMAGE_MULTIPLIER && xCoordinateBase + i * IMAGE_X < width; ++i)
{
// Load memory.
uchar* inputElementOne = (inputOne + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x));
uchar* inputElementTwo = (inputTwo + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x));
// Write output.
*(output + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x)) = inputElementOne[0] + inputElementTwo[0];
}
}
__global__ void and_8uc1(uchar* inputOne, uchar* inputTwo, uchar* output, unsigned int width, unsigned int height, unsigned int widthStep)
{
const int xCoordinateBase = blockIdx.x * IMAGE_X * IMAGE_MULTIPLIER + threadIdx.x;
const int yCoordinate = blockIdx.y * IMAGE_Y + threadIdx.y;
if (yCoordinate >= height)
return;
#pragma unroll IMAGE_MULTIPLIER
for (int i = 0; i < IMAGE_MULTIPLIER && xCoordinateBase + i * IMAGE_X < width; ++i)
{
// Load memory.
uchar* inputElementOne = (inputOne + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x));
uchar* inputElementTwo = (inputTwo + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x));
// Write output.
*(output + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x)) = inputElementOne[0] & inputElementTwo[0];
}
}
// Load memory.
uchar3 inputElementOne = *reinterpret_cast<uchar3*>(inputOne + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x) * 3);
uchar3 inputElementTwo = *reinterpret_cast<uchar3*>(inputTwo + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x) * 3);
// Write output.
*reinterpret_cast<uchar3*>(output + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x) * 3)
= make_uchar3(inputElementOne.x + inputElementTwo.x, inputElementOne.y + inputElementTwo.y, inputElementOne.z + inputElementTwo.z);