gpt4 book ai didi

c++ - CUDA 中的性能

转载 作者:塔克拉玛干 更新时间:2023-11-03 07:29:52 25 4
gpt4 key购买 nike

我似乎无法弄清楚影响内核性能的潜在因素。我实现了两个简单的内核,一个加载两个图像并将它们逐个像素相加,另一个加载两个图像并按位对它们进行ANDS。现在,我将它们模板化,以便内核可以获取 8 位和 32 位图像,以及 1、3 和 4 channel 图像。

所以,最初我让两个内核都将全局内存加载为 uchar3float3,以及 uchar4 等。我不是太但是,由于合并,所以我肯定会使用三元组,所以我想我会给它一个分析运行。我认为,由于操作与 channel 号无关,我可以像读取宽度为三倍的单 channel uchar 图像一样读取图像,而不是 uchar3 图像确实如此。

事实上,uchar3 全局加载比 uchar 加载慢很多,很多。我的努力得到了证明。但是,唉,这只发生在算术内核上。按位与运算显示完全相反的结果!

现在,我知道我可以将图像数据加载为 uint 而不是 uchar,用于按位运算,这应该可以完美地处理合并。但让我们假设我只是想学习和理解正在发生的事情。

让我们忘记 float3float4 等。我的问题是内核的 uchar 版本。那么,简而言之,为什么 uchar 加载有时比 uchar3 加载快,有时则不然?

我使用的是 GTX 470,计算能力 2.0。

附言。根据 CUDA 编程指南,逻辑操作和添加操作具有相同的吞吐量。 (我的内核实际上必须首先将 uchar 转换为 uint,但这应该在两个内核中发生。)所以执行长度应该大致相同, 据我收集。

算术加法内核(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];
}
}

uchar3 版本相同,只是加载/存储行现在如下所示:

        //  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);

与内核类似。 (老实说,我不确定我是否记得确切的内核......我明天会确认)。

最佳答案

uchar3 加载被编译器拆分为单独的加载,因为在 SM 的指令集中没有 24 位加载。因此,它们永远不会合并。在某种程度上,缓存将缓解这种情况。

但是,根据具体的执行配置,每个线程可能只有大约 10.7 字节的缓存(您的示例可能会接近该值,因为内核很简单,所以很多线程可以在一个 SM 上并发运行) .由于缓存不是完全关联的,因此在发生抖动之前,每个线程的可用字节数可能要小得多。确切的发生时间取决于很多因素,包括指令的确切调度,即使对于具有相同记录吞吐量的指令,这也可能不同。

您可以比较两个版本的 cuobjdump -sassexecutable 的输出,看看编译器的静态调度是否相同。然而,运行时的动态调度如何运作基本上是不可观察的。

正如您所注意到的,图像的所有 channel 都以相同的方式处理,因此您如何在线程之间分配它们并不重要。您拥有的最佳选择是使用 uchar4 而不是 uchar3uchar,这(假设图像适当对齐)将为您提供合并访问独立于缓存。这应该会导致更短和更一致的执行时间。

关于c++ - CUDA 中的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13611261/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com