【问题标题】:Low performance kernel低性能内核
【发布时间】:2013-08-28 08:54:14
【问题描述】:

我有一个 CUDA 内核,其中有很多操作和很少的分支。好像

__global__
void kernel(Real *randomValues, Real mu, Real sigma)
{
    int row = blockDim.y * blockIdx.y + threadIdx.y;
    int col = blockDim.x * blockIdx.x + threadIdx.x;

    if ( row >= cnTimeSteps || col >= cnPaths ) return;

    Real alphaLevel = randomValues[row*cnPaths+col];
    Real q = 0.0;
    Real x = 0.0;

    if ( alphaLevel < p_low)
    {
        q = sqrt( -2*log( alphaLevel ) );
        x = (((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q+c6) / ((((d1*q+d2)*q+d3)*q+d4)*q+1);
    }
    else if ( alphaLevel < p_high )
    {
        q = alphaLevel-0.5;
        Real r = q*q;
        x= (((((a1*r+a2)*r+a3)*r+a4)*r+a5)*r+a6)*q / (((((b1*r+b2)*r+b3)*r+b4)*r+b5)*r+1);
    }
    else
    {
        q = sqrt( -2*log( 1.0-alphaLevel ) );
        x = -(((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q+c6) / ((((d1*q+d2)*q+d3)*q+d4)*q+1);
    }

    randomValues[row*cnPaths+col] = sigma * x + mu;
}

其中所有a's、b's、c's 和d's 都是常量值(在设备常量内存中)

static __device__ __constant__ Real a1 = 1.73687;
static __device__ __constant__ Real a2 = 1.12321100;

等等。

分析内核后,我发现理论占用率为 100%,但我得到的不超过 60%。

我经历了thisthis GTC 会谈,试图优化我的内核。

一方面,IPC 报告平均发出 1.32 条指令,执行 0.62 条指令。指令序列化大约是 50%,但 SM 活动几乎是 100%。另一方面,大约有 38 个活动扭曲,但有 8 个有资格执行下一条指令,但在扭曲问题效率方面,我得到大约 70% 的周期没有符合条件的扭曲。失速原因被报告为“其他”,我认为这与logsqrt 的计算有关。

  1. 如果大多数周期都没有符合条件的 warp,SM 活动如何达到 99.82%?
  2. 如何减少失速?
  3. 由于 warp 中的线程可能不会进入同一个分支,因此对常量内存的请求可能会被序列化,这是真的吗?我应该把这些常量放在全局内存中吗(也许也可以使用共享内存)?

我是第一次使用 Nsight Visual Studio,所以我试图弄清楚所有性能分析的含义。顺便说一句,我的卡是 Quadro K4000。

【问题讨论】:

  • 关于您的问题 3,我认为您使用常量内存没有任何问题。这是常量内存的明智应用。翘曲分歧是一个不相关的问题,它本身不会导致对常量内存的访问的任何“序列化”。特定路径上的一个 warp 中的所有线程都在同步执行,并且这些线程都将由给定的常量内存请求同时提供服务,至少在您在此处显示的代码中是这样。
  • (1) 从性能的角度来看,使用文字常量而不是__constant__ 数据可能会更好。 (2) 代码似乎计算了一些数学函数的有理逼近,并且看起来该函数可能与正态分布的误差函数或 CDF 密切相关。如果是这样,请酌情考虑使用 CUDA 的 erf()、erfc()、erfinv()、erfcinv()、normcdf()、normcdfinv() 函数之一。
  • @BRabbit27:对上述近似值的仔细研究强烈表明它们代表了正态分布的累积分布函数的倒数的单精度近似值。 CUDA 有一个内置函数 normcdfinvf()。我建议尝试一下,看看它的使用是否有助于提高这段代码的性能。

标签: cuda gpgpu nvidia


【解决方案1】:

1) 如果大多数周期没有 SM 活动怎么可能是 99.82% 符合条件的经线?

如果寄存器和一个warp 槽被分配给warp,则warp 处于活动状态。 如果 SM 上至少有 1 个经线处于活动状态,则 SM 处于活动状态。

SM 活动不应与效率相混淆。

2) 我怎样才能减少失速?

在上述代码的情况下,warp 会停止等待双精度执行单元可用。 Quadro K4000 的双精度运算吞吐量为 8 个线程/周期。

解决此问题的方法是: 一种。减少双精度操作的数量。例如,将连续操作移动到浮点数可能会显着提高性能,因为单精度浮点吞吐量是双精度吞吐量的 24 倍。 湾。在 GK110 上执行内核,其双精度吞吐量是 GK10x 的 8 倍。

增加已达到的占用率可能不会提高此内核在 K4000 上的性能。您提供的信息不足,无法确定为什么实际入住率明显低于理论入住率。

Achieved FLOPs 实验可用于确认内核性能是否受双精度吞吐量的限制。

3) 由于 warp 中的线程可能不会进入同一个分支,因此对常量内存的请求可能会被序列化,这是真的吗?我应该将这些常量放在全局内存中吗(也许也可以使用共享内存)?

代码在恒定的内存负载中没有内存地址分歧。 Warp 控制流发散只是意味着在每个请求上都会有一部分线程处于活动状态。

初始全局负载可能不会合并。您需要提供 cnPaths 的值以供他人查看。您还可以查看 Memory 实验或 Source Correlated 实验。

if 和 else 语句可能能够以更有效的方式编码,以允许编译器使用谓词而不是分歧分支。

【讨论】:

  • 非常感谢。 2. 我会尝试这两个选项,但我必须先获得 GK110。对于实现的 FLOPs 实验,我得到了大约 40 GFLOPs。我在哪里可以找到 GK104 和 GK110 的单精度和双精度吞吐量? 3. cnPaths 的值可能在 [1e4 - 1e6] 范围内变化,但我认为访问模式会实现合并,除非我忘记了什么。
  • 没关系,我找到了Kepler Tunning Guide
【解决方案2】:

我假设您的 Real 数据类型是 float 的 typedef。您可以尝试将 f 后缀添加到用于防止编译器添加不必要的强制转换的常量值。

例如

q = alphaLevel-0.5;

常量 0.5 是一个双精度值,alphaLevel 是一个 real=float 值。 alphaLevel 将被强制转换为双精度值。 q 是浮点类型。减法的结果必须再次向下转换为浮点数。

如果 Real 是 dobule 的 typedef,您的所有计算都将 double 和 float 混合在一起,从而导致相同的上下转换。

【讨论】:

  • 是的,Real 是双打的 typedef。在这种情况下,铸件将在哪里?我很确定一切都是双重的
  • @BRabbit27 您使用的每个常量,即 a1、a2 等,每次使用都会提升为双精度值。
  • 对不起,我弄错了,常量也是Real,所以不应该有任何提升加倍。
【解决方案3】:

您可以通过简化来减少经线发散的影响:

if ( alphaLevel < p_low)
{
    q = sqrt( -2*log( alphaLevel ) );
    x = (((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q+c6) / ((((d1*q+d2)*q+d3)*q+d4)*q+1);
}
else if ( alphaLevel < p_high )
{
    q = alphaLevel-0.5;
    Real r = q*q;
    x= (((((a1*r+a2)*r+a3)*r+a4)*r+a5)*r+a6)*q / (((((b1*r+b2)*r+b3)*r+b4)*r+b5)*r+1);
}
else
{
    q = sqrt( -2*log( 1.0-alphaLevel ) );
    x = -(((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q+c6) / ((((d1*q+d2)*q+d3)*q+d4)*q+1);
}

到:

if ( alphaLevel >= p_low && alphaLevel < p_high )
{
    q = alphaLevel-0.5;
    Real r = q*q;
    x= (((((a1*r+a2)*r+a3)*r+a4)*r+a5)*r+a6)*q / (((((b1*r+b2)*r+b3)*r+b4)*r+b5)*r+1);
}
else
{
    alphaLevel = alphaLevel >= p_low ? 1.0-alphaLevel : alphaLevel;
    q = sqrt( -2*log( alphaLevel ) );
    x = -(((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q+c6) / ((((d1*q+d2)*q+d3)*q+d4)*q+1);
}

【讨论】:

    猜你喜欢
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    • 2014-02-08
    • 1970-01-01
    • 2013-06-08
    • 2011-03-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多