【问题标题】:Why Global memory version is faster than constant memory in my CUDA code?为什么全局内存版本比我的 CUDA 代码中的常量内存快?
【发布时间】:2013-02-20 20:54:31
【问题描述】:

我正在开发一些 CUDA 程序,我想使用常量内存加快计算速度,但结果发现使用常量内存会使我的代码慢约 30%。

我知道常量内存擅长将读取广播到整个 warp,我认为我的程序可以利用它。

这里是常量内存代码:

__constant__ float4 constPlanes[MAX_PLANES_COUNT];

__global__ void faultsKernelConstantMem(const float3* vertices, unsigned int vertsCount, int* displacements, unsigned int planesCount) {

    unsigned int blockId = __mul24(blockIdx.y, gridDim.x) + blockIdx.x;
    unsigned int vertexIndex = __mul24(blockId, blockDim.x) + threadIdx.x;

    if (vertexIndex >= vertsCount) {
        return;
    }

    float3 v = vertices[vertexIndex];
    int displacementSteps = displacements[vertexIndex];

    //__syncthreads();

    for (unsigned int planeIndex = 0; planeIndex < planesCount; ++planeIndex) {
        float4 plane = constPlanes[planeIndex];
        if (v.x * plane.x + v.y * plane.y + v.z * plane.z + plane.w > 0) {
            ++displacementSteps;
        }
        else {
            --displacementSteps;
        }
    }

    displacements[vertexIndex] = displacementSteps;
}

全局内存代码是相同的,但它多了一个参数(带有指向平面数组的指针)并使用它而不是全局数组。

我认为那些第一个全局内存读取

float3 v = vertices[vertexIndex];
int displacementSteps = displacements[vertexIndex];

可能会导致线程“去同步化”,然后它们将无法利用持续内存读取的广播,因此我尝试调用 __syncthreads();在读取常量内存之前,但它没有改变任何东西。

怎么了?提前致谢!

系统:

  • CUDA 驱动程序版本:5.0
  • CUDA 能力:2.0

参数:

  • 顶点数:~250 万
  • 飞机数量:1024

结果:

  • 常量内存版本:46 毫秒
  • 全局内存版本:35 毫秒

编辑:

所以我尝试了很多如何让常量内存更快的方法,例如:

1) 注释掉两个全局内存读取,看看它们是否有影响,它们没有。全局内存仍然更快。

2) 每个线程处理更多顶点(从 8 个到 64 个)以利用 CM 缓存。这甚至比每个线程一个顶点还要慢。

2b) 使用共享内存来存储位移和顶点 - 在开始时加载所有这些,处理并保存所有位移。同样,比显示的 CM 示例要慢。

经过这次经历,我真的不明白 CM 读取广播是如何工作的,以及如何在我的代码中正确“使用”。这段代码可能无法用 CM 优化。

EDIT2:

又是一天的调整,我试过了:

3) 使用内存合并处理每个线程更多的顶点(8 到 64)(每个线程的增量等于系统中的线程总数)——这比增量等于 1 提供了更好的结果,但仍然没有加速

4) 替换这个 if 语句

if (v.x * plane.x + v.y * plane.y + v.z * plane.z + plane.w > 0) {
    ++displacementSteps;
}
else {
    --displacementSteps;
}

通过少量数学运算给出“不可预测”的结果,以避免使用此代码进行分支:

float dist = v.x * plane.x + v.y * plane.y + v.z * plane.z + plane.w;
int distInt = (int)(dist * (1 << 29));  // distance is in range (0 - 2), stretch it to int range
int sign = 1 | (distInt >> (sizeof(int) * CHAR_BIT - 1));  // compute sign without using ifs
displacementSteps += sign;

不幸的是,这比使用 if 慢很多(~30%),所以 if 并没有我想象的那么大。

EDIT3:

我正在总结这个问题,这个问题可能无法通过使用常量内存来改善,这些是我的结果*:

*时间报告为 15 次独立测量的中值。当常量内存不足以保存所有平面(4096 和 8192)时,内核被多次调用。

【问题讨论】:

  • __syncthreads() 有不同的用途。当您想要同步块级线程时使用它,例如当您使用共享内存时。因为这种情况是没有问题的。

标签: memory optimization cuda


【解决方案1】:

虽然计算能力 2.0 芯片有 64k 的常量内存,但每个多处理器只有 8k 的常量内存缓存。您的代码的每个线程都需要访问所有 16k 的常量内存,因此您会因缓存未命中而损失性能。为了有效地为平面数据使用常量内存,您需要重新构建您的实现。

【讨论】:

  • 此外,使用常量内存的好处来自于常量缓存。缓存的好处来自于数据的重用。内核中没有数据的重用。每次内核调用都会访问常量内存数组中的每个位置一次,且仅访问一次。
  • 对于较少数量的平面,处理时间线性下降(128 个平面使用常数为 5 毫秒,而使用全局则为 4 毫秒)。我的朋友做了非常相似的程序,他们的加速是 60%。我应该尝试为每个线程处理更多顶点吗?但即使没有它,程序也应该更快。
  • @NightElfik:您的算法只是部分使用了 CUDA 架构——使用额外的内核,但没有有效地使用高速片上内存。只有重新设计算法以更好地使用架构才会有所帮助。您的朋友是否使用与您相同的计算能力?
  • @Pieter Geerkens:我知道我可以进一步优化程序,但首先我想解决这个问题,因为如果我使用不同的技术达到加速,这个问题将被“隐藏”。我的朋友们使用相同的计算能力,甚至相同的 GPU。我的代码和他们的代码之间唯一显着的区别是线程开始时读取的 2 次全局内存(他们没有)。
猜你喜欢
  • 2015-05-02
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-20
  • 2018-05-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多