使用动态分配和静态分配的共享内存

时间:2016-05-01 11:03:55

标签: cuda gpu-shared-memory

假设我有两个__device__ CUDA函数,每个函数都有以下局部变量:

__shared__ int a[123];

和另一个函数(说它是我的内核,即__global__函数),用:

extern __shared__ int b[];

nVIDIA是明确允许/禁止的吗? (我在__shared__上的programming guide B.2.3节中没有看到它。这些大小是否一起计入共享内存限制,或者它是一次可能使用的最大值?还是其他一些规则?

这可以被视为this one的后续问题。

1 个答案:

答案 0 :(得分:3)

共享内存分为两部分:静态分配和动态分配。第一部分是在编译期间计算的,每个声明都是一个实际的分配 - 在编译期间激活ptxas信息说明了它:

  ptxas info    : Used 22 registers, 384 bytes smem, 48 bytes cmem[0]

在这里,我们有384个字节,这是332个数组。 (见下面的样本corde)。

您可以将指向共享内存的指针从Kepler传递到另一个允许设备子功能访问另一个共享内存声明的函数。

然后,动态分配共享内存,在内核调用期间声明保留大小。

以下是一些功能中的一些不同用途的示例。请注意每个共享内存区域的指针值。

__device__ void dev1()
{
    __shared__ int a[32] ;
    a[threadIdx.x] = threadIdx.x ;

    if (threadIdx.x == 0)
        printf ("dev1 : %x\n", a) ;
}

__device__ void dev2()
{
    __shared__ int a[32] ;
    a[threadIdx.x] = threadIdx.x * 5 ;

    if (threadIdx.x == 0)
        printf ("dev2 : %x\n", a) ;
}

__global__ void kernel(int* res, int* res2)
{
    __shared__ int a[32] ;
    extern __shared__ int b[];

    a[threadIdx.x] = 0 ;
    b[threadIdx.x] = threadIdx.x * 3 ;

    dev1();
    __syncthreads();
    dev2();
    __syncthreads();

    res[threadIdx.x] = a[threadIdx.x] ;
    res2[threadIdx.x] = b[threadIdx.x] ;

    if (threadIdx.x == 0)
        printf ("global a : %x\n", a) ;
    if (threadIdx.x == 0)
        printf ("global b : %x\n", b) ;
}

int main()
{
    int* dres  ;
    int* dres2 ;

    cudaMalloc <> (&dres, 32*sizeof(int)) ;
    cudaMalloc <> (&dres2, 32*sizeof(int)) ;

    kernel<<<1,32,32*sizeof(float)>>> (dres, dres2);

    int hres[32] ;
    int hres2[32] ;

    cudaMemcpy (hres, dres, 32 * sizeof(int), cudaMemcpyDeviceToHost) ;
    cudaMemcpy (hres2, dres2, 32 * sizeof(int), cudaMemcpyDeviceToHost) ;

    for (int k = 0 ; k < 32 ; ++k)
    {
        printf ("%d -- %d \n", hres[k], hres2[k]) ;
    }
    return 0 ;
}

此代码使用384 bytes smem输出ptxas信息,即一个数组用于全局a数组,第二个用于dev1方法a数组,第三个用于dev2方法{{1数组。总计a

当动态共享内存等于3*32*sizeof(float)=384 bytes运行内核时,指向32*sizeof(float)的指针就在这三个数组之后立即启动。

修改 此代码生成的ptx文件包含静态定义的共享内存的声明,

b

除了在方法体中定义的入口点

.shared .align 4 .b8 _ZZ4dev1vE1a[128];
.shared .align 4 .b8 _ZZ4dev2vE1a[128];
.extern .shared .align 4 .b8 b[];

内存的共享空间在PTX文档here中定义:

  

共享(.shared)状态空间是CTA中用于共享数据的线程的每个CTA内存区域。共享内存中的地址可以由CTA中的任何线程读取和写入。使用ld.shared和st.shared访问共享变量。

虽然运行时没有详细说明。编程指南here中有一个词,没有关于两者混合的进一步细节。

在PTX编译期间,编译器可能知道静态分配的共享内存量。可能会有一些补充魔法。查看SASS,第一条指令使用SR_LMEMHIOFF

// _ZZ6kernelPiS_E1a has been demoted

并以相反的顺序调用函数为静态分配的共享内存分配不同的值(看起来非常像stackalloc的形式)。

我相信ptxas编译器会在最坏的情况下计算可能需要的所有共享内存,因为可能会调用所有方法(当不使用其中一个方法并使用函数指针时,1 IADD32I R1, R1, -0x8; 2 S2R R0, SR_LMEMHIOFF; 3 ISETP.GE.U32.AND P0, PT, R1, R0, PT; 地址不会更改,并且永远不会访问未分配的共享内存区域。

最后,正如einpoklum在评论中所说,这是实验性的,而不是规范/ API定义的一部分。