__threadfence_block()和volatile +共享内存来对抗寄存器

时间:2017-09-12 21:34:01

标签: cuda

问题1

threadfence和volatile可以帮助编译器刷新数据并释放寄存器吗?

问题2

如果共享内存仅用作线程缓存(在线程之间没有使用SMEM交换数据),依赖执行顺序是安全的吗?我的意思是,如果一条指令改变了特定地址中的SMEM,并且代码中后面的一些其他指令在没有任何全局调用的同一线程中读取它,是否需要担心fences / sincronization?

背景

经过一段时间挫败尝试使用内核中的共享内存来缓解寄存器溢出大量嵌套循环后,请注意寄存器计数根本没有变化。 看一下ptxa,我注意到它的发生是因为编译器“延迟”指令的方式是寄存器永远不会自由产生溢出。

在SMEM声明中使用volatile关键字发布了一些寄存器,并且其中一个 hottest 循环中的__threadfence_block()给出了相同的结果,但性能非常小获得(约5%)。

内核代码:

struct __align__(16) ushort8
{
    unsigned short w, x, y, z, k, l, m, n;
};

typedef struct ushort8 ushort8;


__global__ void altMax(const unsigned short nloops, const unsigned short clipper,
    const unsigned short p, const unsigned int npart, const unsigned int stride,
    unsigned short*  Partbondaries,
    ushort8* tpMaxPart, CUdeviceptr* indMax, unsigned long long int* ops) {
    const unsigned short cWarpSize(def_cWarpSize);
// this variable should help to reduce the register pressure
    __shared__ float fel[6][THREADS_MAX];
const int tid(blockDim.x * blockIdx.x + threadIdx.x);
const unsigned int lId(threadIdx.x & 0x1f);
if (tid > npart - 1) return;
const unsigned short rl(Partbondaries[tid] + 1 - def_off);
size_t l_ops(0);

ushort8 el;
int kPos;
float cbMax, ftemp, pb0(0), tl6, tl7, tl8;// , tl[loff + 1];
                                          // alternative pattern midbody [cpu seek]
for (int i = 0; i < nloops - 1; i++) {
    tex3D(&ftemp, ssm3D, Partbondaries[(i)* stride + tid] - 1,
        Partbondaries[(i + 1) * stride + tid] - 1, 0);
    pb0 += ftemp;
}
// alternative pattern tail [cpu seek]
tex3D(&ftemp, ssm3D, Partbondaries[(nloops - 1)* stride + tid] - 1, p - 1, 0);
pb0 += ftemp;
// alternative pattern head [gpu seek]

cbMax = idMax(indMax);
ftemp = 0;
kPos = 0;
for (el.w = 1; el.w < rl + 0; el.w++) {
    if (kPos > 0) tex3D(&ftemp, ssm3D, 0, el.w - 1, 0);
    fel[0][threadIdx.x] = ftemp;
    for (el.x = el.w + 1; el.x < rl + 1; el.x++) {
        if (kPos > 1) tex3D(&ftemp, ssm3D, el.w, el.x - 1, 0);
        ftemp += fel[0][threadIdx.x];
        fel[1][threadIdx.x] = ftemp;
        for (el.y = el.x + 1; el.y < rl + 2; el.y++) {
            if (kPos > 2) tex3D(&ftemp, ssm3D, el.x, el.y - 1, 0);
            ftemp += fel[1][threadIdx.x];
            fel[2][threadIdx.x] = ftemp;
            for (el.z = el.y + 1; el.z < rl + 3; el.z++) {
                if (kPos > 3) tex3D(&ftemp, ssm3D, el.y, el.z - 1, 0);
                ftemp += fel[2][threadIdx.x];
                fel[3][threadIdx.x] = ftemp;
                for (el.k = el.z + 1; el.k < rl + 4; el.k++) {
                    if (kPos > 4) tex3D(&ftemp, ssm3D, el.z, el.k - 1, 0);
                    ftemp += fel[3][threadIdx.x];
                    fel[4][threadIdx.x] = ftemp;
                    for (el.l = el.k + 1; el.l < rl + 5; el.l++) {
                        if (kPos > 5) tex3D(&ftemp, ssm3D, el.k, el.l - 1, 0);
                        ftemp += fel[4][threadIdx.x];
                        fel[5][threadIdx.x] = ftemp;
                        __threadfence_block(); 
                        for (el.m = el.l + 1; el.m < rl + 6; el.m++) {
                            if (kPos > 6) tex3D(&ftemp, ssm3D, el.l, el.m - 1, 0);
                            tl6 = fel[5][threadIdx.x] + ftemp;
                            tl6 += pb0;
                            ftemp = 0;
                            for (el.n = el.m + 1; el.n < rl + 7; el.n++) {
                                tex3D(&tl7, ssm3D, el.m, el.n - 1, 0);
                                // testar a substituição por constante
                                tex3D(&tl8, ssm3D, el.n, rl - 1, 0); // tem q ser conferido
                                tl8 += tl7;
                                l_ops++;
                                if (tl8 > ftemp) {
                                    ftemp = tl8;
                                    kPos = el.n;
                                }
                            }
                            ftemp += tl6;
                            if (ftemp > cbMax) {
                                el.n = kPos;
                                cbMax = ftemp;
                                tpMaxPart[tid] = el;
                            }
                        }
                        kPos = 6;
                    }
                    kPos = 5;
                }
                kPos = 4;
            }
            kPos = 3;
        }
        kPos = 2;
    }
    kPos = 1;
}
// warp lvl reduction
unsigned short maxtd, ttd;
maxtd = lId;
#pragma unroll 
for (int i = 1; cWarpSize > i; i *= 2) {
    pb0 = __shfl_down_sync(UINT32_MAX, cbMax, i, cWarpSize);
    ttd = __shfl_down_sync(UINT32_MAX, maxtd, i, cWarpSize);
    l_ops += __shfl_xor_sync(UINT32_MAX, l_ops, i, cWarpSize);
    if (pb0 > cbMax) {
        cbMax = pb0;
        maxtd = ttd;
    }
}
maxtd = __shfl_sync(UINT32_MAX, maxtd, 0, cWarpSize);

// tem q conferir se todos os valores estão realmente sincronizando td
if (lId == maxtd) {
    atomicAdd(ops, l_ops);
    idMax(indMax, cbMax, tid);
}

}

1 个答案:

答案 0 :(得分:1)

  

threadfence和volatile可以帮助编译器刷新数据并释放寄存器吗?

可能在某些情况下。您似乎已在您的问题中建议您已确认是这种情况。我通常认为这不是一种多变的高效形式(与编译器作斗争),但这仅仅是一种观点或个人偏好。这里实际上没有足够的实验或提供具体的答案。

以这种方式“释放”寄存器只是为某种形式的数据加载/存储流量交换寄存器。这通常不是胜利,编译器通常会试图避免这种情况。您可能已经找到了一个可以做得稍好的情况。这种编译器优化过程可能相当复杂,并且当前的技术水平不保证最优性。它只是试图在合理的计算时间内实现这一点。如果您认为您找到了一个令人震惊的反例,那么您可能需要在developer.nvidia.com上提交一个错误,并提供必要的完整可编码代码以查看问题,以及确定用于比较的两个案例。当然,欢迎您在任何情况下提交错误,但我不确定5%的观察会引起很多关注。

  

如果共享内存仅用作线程缓存(在线程之间没有使用SMEM交换数据),依赖执行顺序是安全的吗?我的意思是,如果一条指令改变了特定地址中的SMEM,并且代码中后面的一些其他指令在没有任何全局调用的同一线程中读取它,是否需要担心fences / sincronization?

如果共享内存使用仅限于单个线程(即不使用共享内存在线程之间共享数据),则无需担心防护或同步。在这种情况下,单线程C / C ++编程模型适用,您可以确信如果线程将值保存到共享内存然后稍后加载该值,它将获得正确的值。