cuda学习7

7. C++ Language ExtensionsC++语言扩展

7.1. Function Execution Space Specifiers

函数执行空间说明符表示函数是在主机上执行还是在设备上执行,以及它是可从主机调用还是可从设备调用。

7.1.1. __global__

__global__执行空间说明符将函数声明为内核。这样的函数是:

  • Executed on the device,在设备上执行

  • Callable from the host,可从主机调用

  • 对于计算能力为5.0或更高版本的设备,可从设备调用(有关详细信息,请参阅CUDA动态并行性)。

__global__函数必须具有void返回类型,并且不能是类的成员

__global__函数的任何调用都必须指定其执行配置,如中所述 执行配置.

__global__函数的调用是异步的,这意味着它在设备完成执行之前返回

7.1.2. __device__

__device__执行空间说明符声明一个函数,该函数:

  • Executed on the device,在设备上执行,

  • Callable from the device only.

__global____device__执行空间说明符不能一起使用。

7.1.3. __host__

__host__执行空间说明符声明一个函数,该函数:
在主机上执行,
只能从主机调用。

这相当于只使用__host__执行空间说明符声明函数,或者不使用__host__、__device__或__global__执行空间说明符声明函数;在任一种情况下,仅为主机编译函数
__global__和__host__执行空间说明符不能一起使用
但是,__device__和__host__执行空间说明符可以一起使用,在这种情况下,函数将同时针对主机和设备进行编译。应用程序兼容性中介绍的__CUDA_ARCH__宏可用于区分主机和设备之间的代码路径:

__host__ __device__ func()
{
#if __CUDA_ARCH__ >= 800
   // Device code path for compute capability 8.x
#elif __CUDA_ARCH__ >= 700
   // Device code path for compute capability 7.x
#elif __CUDA_ARCH__ >= 600
   // Device code path for compute capability 6.x
#elif __CUDA_ARCH__ >= 500
   // Device code path for compute capability 5.x
#elif !defined(__CUDA_ARCH__)
   // Host code path
#endif
}

7.1.4. Undefined behavior

在以下情况下,“交叉执行空间”调用具有未定义的行为:
定义了__CUDA_ARCH__,从__global____device__ or __host__ __device__函数内部调用__host__。
__CUDA_ARCH__未定义,从__host__函数内部调用__device__函数。9

7.1.5. __noinline__ and __forceinline

编译器在认为合适时内联任何__device__函数。

__noinline__函数限定符可以用作编译器的提示,如果可能的话,不要内联函数。

__forceinline__函数限定符可以用来强制编译器内联函数。

__noinline____forceinline__函数限定符不能一起使用,两个函数限定符都不能应用于内联函数。

7.2. Variable Memory Space Specifiers

在设备代码中声明的、没有本节所述的__device____shared____constant__存储器空间说明符中的任何一个的自动变量通常驻留在寄存器中。但是,在某些情况下,编译器可能会选择将其放置在本地内存中,这可能会对性能产生不利影响,详见设备内存访问

7.2.1. __device__

__device__内存空间说明符声明驻留在设备上的变量

在接下来的三个部分中定义的其他内存空间说明符中的至多一个可以与__device__一起使用,以进一步表示变量属于哪个内存空间。如果它们都不存在,则变量:

  • 驻留在全局内存空间中,

  • 具有创建它的CUDA上下文的生命周期,

  • 每个设备具有不同的对象,

  • 可从网格内的所有线程以及主机通过运行时库(cudaGetSymbolAddress() / cudaGetSymbolSize() / cudaMemcpyToSymbol() / cudaMemcpyFromSymbol()访问)。

7.2.2. __constant__

__constant__内存空间说明符(可选择与__device__一起使用)声明一个变量,该变量:

Resides in constant memory space,驻留在恒定的存储空间中

  • 具有创建它的CUDA上下文的生命周期,
  • 每个设备具有不同的对象,
  • 可从网格内的所有线程以及主机通过运行时库(cudaGetSymbolAddress()/ cudaGetSymbolSize()/ cudaMemcpyToSymbol()/ cudaMemcpyFromSymbol())进行访问。

7.2.3. __shared__

__shared__内存空间说明符(可选地与__device__一起使用)声明了一个变量:

  • Resides in the shared memory space of a thread block,驻留在线程块的共享内存空间中,,

  • Has the lifetime of the block,具有块的生命周期,

  • Has a distinct object per block,每个块都有一个不同的对象,

  • Is only accessible from all the threads within the block,
    只能从块内的所有线程访问,

  • Does not have a constant address.没有固定地址。

7.2.4. __grid_constant__

用于大于或等于7.0的计算架构的__grid_constant__注释注释非引用类型的const限定的__global__函数参数,其:

  • Has the lifetime of the grid,具有grid的寿命,

  • Is private to the grid, i.e., the object is not accessible to host threads and threads from other grids, including sub-grids,
    对网格是私有的,即,该对象对于主机线程和来自其它网格的线程是不可访问的,包括子网格,

  • Has a distinct object per grid, i.e., all threads in the grid see the same address,
    每个网格都有一个不同的对象,即,网格中的所有线程看到相同的地址,

  • Is read-only, i.e., modifying a __grid_constant__ object or any of its sub-objects is undefined behavior, including mutable members.
    Is read-only, i.e., modifying a __grid_constant__ object or any of its sub-objects is undefined behavior, including mutable members.

7.2.5. __managed__

__managed__内存空间说明符(可选择与__device__一起使用)声明一个变量,该变量:
__constant__内存空间说明符(可选择与__device__一起使用)声明一个变量,该变量:

  • 可以从设备和主机代码引用,例如,可以获取其地址,也可以直接从设备或主机函数读取或写入。

  • Has the lifetime of an application.具有应用程序的生存期。

7.2.6. __restrict__

nvcc通过__restrict__关键字支持受限指针。

C99中引入了受限指针,以缓解C类型语言中存在的别名问题,该问题抑制了从代码重新排序到公共子表达式消除的所有类型的优化。

下面是一个受别名问题影响的示例,其中使用受限指针可以帮助编译器减少指令数:

void foo(const float* a,
         const float* b,
         float* c)
{
    c[0] = a[0] * b[0];
    c[1] = a[0] * b[0];
    c[2] = a[0] * b[0] * a[1];
    c[3] = a[0] * a[1];
    c[4] = a[0] * b[0];
    c[5] = b[0];
    ...
}

在C类型语言中,指针abc可以被别名化,因此通过c的任何写入可以修改ab的元素。这意味着为了保证功能正确性,编译器不能将a[0]b[0]加载到寄存器中,将它们相乘,并将结果存储到c[0]c[1],因为如果a[0]c[0]实际上是相同的位置,则结果将与抽象执行模型不同。因此编译器不能利用公共子表达式。同样地,编译器不能仅仅将c[4]的计算重新排序到c[0]c[1]的计算附近,因为之前对c[3]的写入可能改变对c[4]的计算的输入。

通过使abc受限指针,程序员向编译器断言这些指针实际上没有别名,在这种情况下,这意味着通过c的写入永远不会覆盖ab的元素。这将更改函数原型,如下所示:

void foo(const float* __restrict__ a,
         const float* __restrict__ b,
         float* __restrict__ c);

请注意,需要限制所有指针参数,以便编译器优化器获得任何好处。通过添加__restrict__关键字,编译器现在可以重新排序并随意执行公共子表达式消除,同时保留与抽象执行模型相同的功能:

void foo(const float* __restrict__ a,
         const float* __restrict__ b,
         float* __restrict__ c)
{
    float t0 = a[0];
    float t1 = b[0];
    float t2 = t0 * t1;
    float t3 = a[1];
    c[0] = t2;
    c[1] = t2;
    c[4] = t2;
    c[2] = t2 * t3;
    c[3] = t0 * t3;
    c[5] = t1;
    ...
}

其效果是减少了内存访问次数和计算次数。这通过“缓存”加载和公共子表达式导致的寄存器压力增加来平衡。

由于寄存器压力在许多CUDA代码中是一个关键问题,因此使用受限指针会降低占用率,从而对CUDA代码的性能产生负面影响。

7.3. Built-in Vector Types

7.3.1. char, short, int, long, longlong, float, double

这些是从基本整数和浮点类型派生的向量类型。它们是结构,并且 1st, 2nd, 3rd, and 4th件可分别通过字段xyzw访问。它们都带有一个构造函数,其形式为make_<type name>;例如,

int2 make_int2(int x, int y);

其创建具有值int2的类型(x, y)的向量。
载体类型的比对要求详见表5

Type

Alignment

char1, uchar1

1

char2, uchar2

2

char3, uchar3

1

char4, uchar4

4

short1, ushort1

2

short2, ushort2

4

short3, ushort3

2

short4, ushort4

8

int1, uint1

4

int2, uint2

8

int3, uint3

4

int4, uint4

16

long1, ulong1

4 if sizeof(long) is equal to sizeof(int) 8, otherwise

long2, ulong2

8 if sizeof(long) is equal to sizeof(int), 16, otherwise

long3, ulong3

4 if sizeof(long) is equal to sizeof(int), 8, otherwise

long4, ulong4

16

longlong1, ulonglong1

8

longlong2, ulonglong2

16

longlong3, ulonglong3

8

longlong4, ulonglong4

16

float1

4

float2

8

float3

4

float4

16

double1

8

double2

16

double3

8

double4

16

7.3.2. dim3

此类型是基于uint3的整数向量类型,用于指定维度。当定义dim3类型的变量时,任何未指定的分量都被初始化为1。

7.4. Built-in Variables

内置变量指定栅格和块尺寸以及块和线程索引。它们仅在设备上执行的功能中有效。

7.4.1. gridDim

此变量的类型为dim3(参见dim3),包含网格的尺寸

7.4.2. blockIdx

这个变量的类型是uint3(参见char,short,int,long,longlong,float,double),它包含网格中的块索引。

7.4.3. blockDim

此变量的类型为dim3(参见dim3),包含块的尺寸。

7.4.4. threadIdx

这个变量的类型是uint3(参见char,short,int,long,longlong,float,double),它包含了块中的线程索引。

7.4.5. warpSize

此变量的类型为int,包含线程中的This variable is of type int and contains the warp size in threads (see SIMT Architecture for the definition of a warp).


7.5. Memory Fence Functions

CUDA编程模型假设设备具有弱有序内存模型,即CUDA线程将数据写入共享内存、全局内存、页面锁定主机内存或对等设备内存的顺序不一定是观察到另一CUDA或主机线程写入数据的顺序。两个线程在不同步的情况下读取或写入同一内存位置是未定义的行为。

在下面的示例中,线程1执行writeXY(),而线程2执行readXY()

__device__ int X = 1, Y = 2;

__device__ void writeXY()
{
    X = 10;
    Y = 20;
}

__device__ void readXY()
{
    int B = Y;
    int A = X;
}

两个线程同时从相同的存储器位置XY读取和写入。任何数据争用都是未定义的行为,并且没有定义的语义。AB的结果值可以是任何值。

存储器围栏函数可用于对存储器存取实施顺序一致的排序。内存围栏函数的不同之处在于执行排序的范围,但它们独立于访问的内存空间(共享内存、全局内存、页锁定主机内存和对等设备的内存)。

void __threadfence_block();

等效于cuda::atomic_thread_fence(cuda::memory_order_seq_cst, cuda::thread_scope_block) and ensures that:

  • 在调用__threadfence_block()之前由调用线程对所有存储器进行的所有写入被调用线程的块中的所有线程观察为在调用__threadfence_block()之后由调用线程对所有存储器进行的所有写入之前发生;

  • 调用线程在调用__threadfence_block()之前从所有存储器进行的所有读取被排序在调用线程在调用__threadfence_block()之后从所有存储器进行的所有读取之前。

void __threadfence();

等同于cuda::atomic_thread_fence(cuda::memory_order_seq_cst,cuda::thread_scope_device),并且确保在调用__threadfence()之后由调用线程对所有存储器进行的任何写入都不会被设备中的任何线程观察到,因为在调用__threadfence()之前由调用线程对所有存储器进行的任何写入都不会发生。

void __threadfence_system();

等效于cuda::atomic_thread_fence(cuda::memory_order_seq_cst,cuda::thread_scope_system),并确保在调用__threadfence_system()之前由调用线程对所有存储器进行的所有写入被设备中的所有线程、主机线程和对等设备中的所有线程观察为在调用__threadfence_system()之后由调用线程对所有存储器进行的所有写入之前发生。

__threadfence_system()仅受计算能力为2.x及更高版本的设备支持。

在前面的代码示例中,我们可以在代码中插入围栏,如下所示:

__device__ int X = 1, Y = 2;

__device__ void writeXY()
{
    X = 10;
    __threadfence();
    Y = 20;
}

__device__ void readXY()
{
    int B = Y;
    __threadfence();
    int A = X;

对于此代码,可以观察到以下结果:

  • A equal to 1 and B equal to 2,
    A等于1且B等于2,

  • A equal to 10 and B equal to 2,
    A等于10且B等于2,

  • A equal to 10 and B equal to 20.
    A等于10,B等于20。

第四种结果是不可能的,因为第一次写入必须在第二次写入之前可见。如果线程1和2属于同一个块,使用__threadfence_block()就足够了。如果线程1和2不属于同一个块,则如果它们是来自同一设备的CUDA线程,则必须使用__threadfence();如果它们是来自两个不同设备的CUDA线程,则必须使用__threadfence_system()

常见的用例是线程使用其他线程生成的一些数据,如以下内核代码示例所示,该内核在一次调用中计算N个数字的数组之和。每个块首先对数组的子集求和,并将结果存储在全局内存中。当所有块都完成时,最后完成的块从全局存储器读取这些部分和中的每一个,并将它们相加以获得最终结果。为了确定哪个块最后完成,每个块原子地递增计数器,以通知它已经完成计算并存储其部分和(参见原子函数关于原子函数)。最后一个块是接收等于gridDim.x-1的计数器值的块。如果在存储部分和与递增计数器之间未放置栅栏,那么计数器可在存储部分和之前递增,且因此可达到gridDim.x-1且让最后块在部分和已在存储器中实际更新之前开始阅读部分和。

内存围栏函数只影响线程对内存操作的排序; 它们本身并不确保这些内存操作对其他线程可见(就像__syncthreads()对块内的线程所做的那样(请参见同步功能)).在下面的代码示例中,通过将result变量声明为volatile来确保对该变量的内存操作的可见性(请参见 Volatile限定符).

__device__ unsigned int count = 0;
__shared__ bool isLastBlockDone;
__global__ void sum(const float* array, unsigned int N,
                    volatile float* result)
{
    // Each block sums a subset of the input array.
    float partialSum = calculatePartialSum(array, N);

    if (threadIdx.x == 0) {

        // Thread 0 of each block stores the partial sum
        // to global memory. The compiler will use
        // a store operation that bypasses the L1 cache
        // since the "result" variable is declared as
        // volatile. This ensures that the threads of
        // the last block will read the correct partial
        // sums computed by all other blocks.
        result[blockIdx.x] = partialSum;

        // Thread 0 makes sure that the incrementation
        // of the "count" variable is only performed after
        // the partial sum has been written to global memory.
        __threadfence();

        // Thread 0 signals that it is done.
        unsigned int value = atomicInc(&count, gridDim.x);

        // Thread 0 determines if its block is the last
        // block to be done.
        isLastBlockDone = (value == (gridDim.x - 1));
    }

    // Synchronize to make sure that each thread reads
    // the correct value of isLastBlockDone.
    __syncthreads();

    if (isLastBlockDone) {

        // The last block sums the partial sums
        // stored in result[0 .. gridDim.x-1]
        float totalSum = calculateTotalSum(result);

        if (threadIdx.x == 0) {

            // Thread 0 of last block stores the total sum
            // to global memory and resets the count
            // varialble, so that the next kernel call
            // works properly.
            result[0] = totalSum;
            count = 0;
        }
    }
}

7.6. Synchronization Functions

void __syncthreads();

等待,直到线程块中的所有线程都到达该点,并且这些线程在__syncthreads()之前进行的所有全局和共享内存访问对块中的所有线程都可见。

__syncthreads()用于协调同一块的线程之间的通信。当一个块中的一些线程访问共享或全局内存中的相同地址时,其中一些内存访问可能会出现写后读、读后写或写后写危险。通过同步这些访问之间的线程,可以避免这些数据冲突。

__syncthreads()在条件代码中是允许的,但仅当条件在整个线程块中的计算结果相同时,否则代码执行可能会挂起或产生意外的副作用。

计算能力2.x及更高的设备支持以下描述的__syncthreads()的三个变体。

int __syncthreads_count(int predicate);

__syncthreads()相同,但有一个附加特性,即它为块的所有线程计算谓词,并返回谓词计算为非零的线程数。

int __syncthreads_and(int predicate);

__syncthreads()相同,具有额外的功能,即它为块的所有线程计算谓词,并且当且仅当谓词为所有线程计算为非零时返回非零。

int __syncthreads_or(int predicate);

__syncthreads()相同,但具有额外的功能,即它为块的所有线程计算谓词,当且仅当谓词为其中任何线程计算为非零时返回非零。

void __syncwarp(unsigned mask=0xffffffff);

将使执行线程等待,直到掩码中命名的所有线程束通道在恢复执行之前已经执行了__syncwarp()(具有相同的掩码)。每个调用线程必须在掩码中设置自己的位,并且掩码中命名的所有未退出线程必须使用相同的掩码执行相应的__syncwarp(),否则结果未定义。

执行__syncwarp()保证了参与屏障的线程之间的存储器排序。因此,线程束内希望经由存储器通信的线程可存储到存储器,执行__syncwarp(),且接着安全地读取由线程束中的其它线程存储的值。

7.7. Mathematical Functions

参考手册列出了设备代码支持的所有C/C++标准库数学函数,以及仅设备代码支持的所有内在函数。

“数学函数”提供了其中某些函数的相关精度信息。

7.8. Texture Functions纹理函数

中介绍了纹理对象 纹理对象API

中介绍了纹理提取 纹理提取.

7.8.1. Texture Object AP‘

7.8.1.1. tex1Dfetch()

template<class T>
T tex1Dfetch(cudaTextureObject_t texObj, int x);

使用整数纹理坐标texObj从由一维纹理对象x指定的线性存储器的区域中提取tex1Dfetch()仅适用于非归一化坐标,因此仅支持边界和箝位寻址模式。它不执行任何纹理过滤。对于整数类型,它可以选择将整数提升为单精度浮点。

7.8.1.2. tex1D()

template<class T>
T tex1D(cudaTextureObject_t texObj, float x);

使用纹理坐标texObj从由一维纹理对象x指定的CUDA数组中获取。

7.8.1.3. tex1DLod()

template<class T>
T tex1DLod(cudaTextureObject_t texObj, float x, float level);

从由一维纹理对象texObj指定的CUDA数组中使用纹理坐标x在细节层次level处进行提取。

7.8.1.4. tex1DGrad()

template<class T>
T tex1DGrad(cudaTextureObject_t texObj, float x, float dx, float dy);

使用纹理坐标texObj从由一维纹理对象x指定的CUDA数组中获取。从X梯度dx和Y梯度dy导出细节层次

7.8.1.5. tex2D()

template<class T>
T tex2D(cudaTextureObject_t texObj, float x, float y);

使用纹理坐标texObj从CUDA数组或由二维纹理对象(x,y)指定的线性存储器的区域中提取。

7.8.1.6. tex2D() for sparse CUDA arrays

      template<class T>
T tex2D(cudaTextureObject_t texObj, float x, float y, bool* isResident);

使用纹理坐标texObj从二维纹理对象(x,y)指定的CUDA数组中获取。还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.7. tex2Dgather()

template<class T>
T tex2Dgather(cudaTextureObject_t texObj,
              float x, float y, int comp = 0);

使用纹理坐标texObjx以及如中所述的y参数从由2D纹理对象comp指定的CUDA数组中提取 纹理聚集.

7.8.1.8. tex2Dgather() for sparse CUDA arraystex2Dgather()

                template<class T>
T tex2Dgather(cudaTextureObject_t texObj,
            float x, float y, bool* isResident, int comp = 0);

使用纹理坐标texObjx以及如中所述的y参数从由2D纹理对象comp指定的CUDA数组中提取 纹理聚集.还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.9. tex2DGrad()

template<class T>
T tex2DGrad(cudaTextureObject_t texObj, float x, float y,
            float2 dx, float2 dy);

使用纹理坐标(x,y)从二维纹理对象texObj指定的CUDA数组中获取。细节层次是从dx和dy梯度导出的。

7.8.1.10. tex2DGrad() for sparse CUDA array

              template<class T>
T tex2DGrad(cudaTextureObject_t texObj, float x, float y,
        float2 dx, float2 dy, bool* isResident);

使用纹理坐标texObj从二维纹理对象(x,y)指定的CUDA数组中获取。细节层次从dxdy梯度导出。还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.11. tex2DLod()

template<class T>
tex2DLod(cudaTextureObject_t texObj, float x, float y, float level);


使用纹理坐标texObj在细节级别(x,y)从CUDA阵列或由二维纹理对象level指定的线性存储器的区域中提取。

7.8.1.12. tex2DLod() for sparse CUDA arrays

     template<class T>
tex2DLod(cudaTextureObject_t texObj, float x, float y, float level, bool* isResident);

从由二维纹理对象texObj指定的CUDA数组中使用纹理坐标(x,y)在细节级别level处进行提取。还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.13. tex3D()

template<class T>
T tex3D(cudaTextureObject_t texObj, float x, float y, float z);

使用纹理坐标texObj从由三维纹理对象(x,y,z)指定的CUDA数组中获取。

7.8.1.14. tex3D() for sparse CUDA arrays

                template<class T>
T tex3D(cudaTextureObject_t texObj, float x, float y, float z, bool* isResident);

使用纹理坐标texObj从由三维纹理对象(x,y,z)指定的CUDA数组中获取。还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.15. tex3DLod()tex3DLod()

template<class T>
T tex3DLod(cudaTextureObject_t texObj, float x, float y, float z, float level);

使用纹理坐标texObj在细节级别(x,y,z)从CUDA阵列或由三维纹理对象level指定的线性存储器的区域中提取。

7.8.1.16. tex3DLod() for sparse CUDA arrays

                template<class T>
T tex3DLod(cudaTextureObject_t texObj, float x, float y, float z, float level, bool* isResident);

使用纹理坐标texObj在细节级别(x,y,z)从CUDA阵列或由三维纹理对象level指定的线性存储器的区域中提取。还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.17. tex3DGrad()

template<class T>
T tex3DGrad(cudaTextureObject_t texObj, float x, float y, float z,
            float4 dx, float4 dy);

使用纹理坐标texObj以从X和Y梯度(x,y,z)dx导出的细节级别从由三维纹理对象dy指定的CUDA阵列中提取。

7.8.1.18. tex3DGrad() for sparse CUDA arraystex3DGrad()用于稀疏CUDA数组

   template<class T>
T tex3DGrad(cudaTextureObject_t texObj, float x, float y, float z,
        float4 dx, float4 dy, bool* isResident);

使用纹理坐标texObj以从X和Y梯度(x,y,z)dx导出的细节级别从由三维纹理对象dy指定的CUDA阵列中提取。还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.19. tex1DLayered()

template<class T>
T tex1DLayered(cudaTextureObject_t texObj, float x, int layer);

使用纹理坐标texObj和索引x从由一维纹理对象layer指定的CUDA数组中提取,如 分层纹理

7.8.1.20. tex1DLayeredLod()

template<class T>
T tex1DLayeredLod(cudaTextureObject_t texObj, float x, int layer, float level);

从一维数组指定的CUDA数组中读取 层状结构在层layer处使用纹理坐标x和细节级别level

7.8.1.21. tex1DLayeredGrad()

template<class T>
T tex1DLayeredGrad(cudaTextureObject_t texObj, float x, int layer,
                   float dx, float dy);

从一维数组指定的CUDA数组中读取 层状结构在层layer处使用纹理坐标x和从dxdy梯度导出的细节水平。

7.8.1.22. tex2DLayered()

template<class T>
T tex2DLayered(cudaTextureObject_t texObj,
               float x, float y, int layer);

使用纹理坐标texObj和索引(x,y)从由二维纹理对象layer指定的CUDA数组中提取,如 分层纹理.

7.8.1.23. tex2DLayered() for sparse CUDA arrays

 
 
                template<class T>
T tex2DLayered(cudaTextureObject_t texObj,
            float x, float y, int layer, bool* isResident);


使用纹理坐标texObj和索引(x,y)从由二维纹理对象layer指定的CUDA数组中提取,如 分层纹理.还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.24. tex2DLayeredLod()

template<class T>
T tex2DLayeredLod(cudaTextureObject_t texObj, float x, float y, int layer,
                  float level);

从二维数组指定的CUDA数组中读取 层状结构在层layer使用纹理坐标(x,y)

7.8.1.25. tex2DLayeredLod() for sparse CUDA arrays

 template<class T>
T tex2DLayeredLod(cudaTextureObject_t texObj, float x, float y, int layer,
                float level, bool* isResident);

从二维数组指定的CUDA数组中读取 层状结构在层layer使用纹理坐标(x,y)。还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.26. tex2DLayeredGrad()

template<class T>
T tex2DLayeredGrad(cudaTextureObject_t texObj, float x, float y, int layer,
                   float2 dx, float2 dy);


从二维数组指定的CUDA数组中读取 层状结构在层layer处使用纹理坐标(x,y)和从dxdy梯度导出的细节水平。

7.8.1.27. tex2DLayeredGrad() for sparse CUDA arrays

 
 
 template<class T>
T tex2DLayeredGrad(cudaTextureObject_t texObj, float x, float y, int layer,
                float2 dx, float2 dy, bool* isResident);

从二维数组指定的CUDA数组中读取 层状结构在层layer处使用纹理坐标(x,y)和从dxdy梯度导出的细节水平。还通过isResident指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。

7.8.1.28. texCubemap()

template<class T>
T texCubemap(cudaTextureObject_t texObj, float x, float y, float z);

7.8.1.29. texCubemapGrad()

template<class T>
T texCubemapGrad(cudaTextureObject_t texObj, float x, float, y, float z,
                float4 dx, float4 dy);

使用纹理坐标texObj从立方体贴图纹理对象(x,y,z)指定的CUDA数组中获取,如中所述 立方体图纹理.所使用的细节层次源自dxdy梯度。

7.8.1.30. texCubemapLod()texCubemapLod()

template<class T>
T texCubemapLod(cudaTextureObject_t texObj, float x, float, y, float z,
                float level);

使用纹理坐标texObj从立方体贴图纹理对象(x,y,z)指定的CUDA数组中获取,如中所述 立方体图纹理.所使用的细节级别由level给出。

7.8.1.31. texCubemapLayered()

template<class T>
T texCubemapLayered(cudaTextureObject_t texObj,
                    float x, float y, float z, int layer);

使用纹理坐标texObj和索引(x,y,z)从立方体贴图分层纹理对象layer指定的CUDA数组中提取,如立方体贴图分层纹理中所述

7.8.1.32. texCubemapLayeredGrad()

template<class T>
T texCubemapLayeredGrad(cudaTextureObject_t texObj, float x, float y, float z,
                       int layer, float4 dx, float4 dy);

使用纹理坐标texObj和索引(x,y,z)从立方体贴图分层纹理对象layer指定的CUDA数组中提取,如立方体贴图分层纹理中所述,细节级别从dxdy渐变派生。

7.8.1.33. texCubemapLayeredLod()

template<class T>
T texCubemapLayeredLod(cudaTextureObject_t texObj, float x, float y, float z,
                       int layer, float level);

使用纹理坐标texObj和索引(x,y,z)从立方体贴图分层纹理对象layer指定的CUDA数组中提取,如立方体贴图分层纹理中所述,在细节级别level

7.9. Surfurface Functions曲面函数

中介绍了曲面对象 曲面对象API

在下面的部分中,boundaryMode指定边界模式,即如何处理超出范围的表面坐标;它等于cudaBoundaryModeClamp,在这种情况下,超出范围的坐标被箝位到有效范围,或者等于cudaBoundaryModeZero,在这种情况下,超出范围的读取返回零并且超出范围的写入被忽略,或者等于cudaBoundaryModeTrap,在这种情况下,超出范围的访问导致内核执行失败。

7.9.1. Surface Object API

7.9.1.1. surf1Dread()

template<class T>
T surf1Dread(cudaSurfaceObject_t surfObj, int x,
               boundaryMode = cudaBoundaryModeTrap);

使用字节坐标x读取由一维表面对象surfObj指定的CUDA数组。

7.9.1.2. surf1Dwrite

template<class T>
void surf1Dwrite(T data,
                  cudaSurfaceObject_t surfObj,
                  int x,
                  boundaryMode = cudaBoundaryModeTrap);

将值数据写入由字节坐标x处的一维表面对象surfObj指定的CUDA阵列。

7.9.1.3. surf2Dread()

template<class T>
T surf2Dread(cudaSurfaceObject_t surfObj,
              int x, int y,
              boundaryMode = cudaBoundaryModeTrap);
template<class T>
void surf2Dread(T* data,
                 cudaSurfaceObject_t surfObj,
                 int x, int y,
                 boundaryMode = cudaBoundaryModeTrap);

使用字节坐标x和y读取由二维表面对象surfObj指定的CUDA数组。

7.9.1.4. surf2Dwrite()

template<class T>
void surf2Dwrite(T data,
                  cudaSurfaceObject_t surfObj,
                  int x, int y,
                  boundaryMode = cudaBoundaryModeTrap);

将值数据写入由字节坐标x和y处的二维表面对象surfObj指定的CUDA数组。

7.9.1.5. surf3Dread()

template<class T>
T surf3Dread(cudaSurfaceObject_t surfObj,
              int x, int y, int z,
              boundaryMode = cudaBoundaryModeTrap);
template<class T>
void surf3Dread(T* data,
                 cudaSurfaceObject_t surfObj,
                 int x, int y, int z,
                 boundaryMode = cudaBoundaryModeTrap);

使用字节坐标x、y和z读取由三维表面对象surfObj指定的CUDA数组。

7.9.1.6. surf3Dwrite()

template<class T>
void surf3Dwrite(T data,
                  cudaSurfaceObject_t surfObj,
                  int x, int y, int z,
                  boundaryMode = cudaBoundaryModeTrap);

将值数据写入由三维对象surfObj在字节坐标x、y和z处指定的CUDA数组。

7.9.1.7. surf1DLayeredread()

template<class T>
T surf1DLayeredread(
                 cudaSurfaceObject_t surfObj,
                 int x, int layer,
                 boundaryMode = cudaBoundaryModeTrap);
template<class T>
void surf1DLayeredread(T data,
                 cudaSurfaceObject_t surfObj,
                 int x, int layer,
                 boundaryMode = cudaBoundaryModeTrap);

使用字节坐标x和索引surfObj读取由一维分层表面对象layer指定的CUDA阵列。

7.9.1.8. surf1DLayeredwrite()

template<class Type>
void surf1DLayeredwrite(T data,
                 cudaSurfaceObject_t surfObj,
                 int x, int layer,
                 boundaryMode = cudaBoundaryModeTrap);

将值数据写入由字节坐标x和索引surfObj处的二维分层表面对象layer指定的CUDA阵列。

7.9.1.9. surf2DLayeredread()

template<class T>
T surf2DLayeredread(
                 cudaSurfaceObject_t surfObj,
                 int x, int y, int layer,
                 boundaryMode = cudaBoundaryModeTrap);
template<class T>
void surf2DLayeredread(T data,
                         cudaSurfaceObject_t surfObj,
                         int x, int y, int layer,
                         boundaryMode = cudaBoundaryModeTrap);

使用字节坐标x和y以及索引surfObj读取由二维分层表面对象layer指定的CUDA阵列。

7.9.1.10. surf2DLayeredwrite()

template<class T>
void surf2DLayeredwrite(T data,
                          cudaSurfaceObject_t surfObj,
                          int x, int y, int layer,
                          boundaryMode = cudaBoundaryModeTrap);

将值数据写入由字节坐标x和y处的一维分层表面对象surfObj和索引layer指定的CUDA阵列。

7.9.1.11. surfCubemapread()

template<class T>
T surfCubemapread(
                 cudaSurfaceObject_t surfObj,
                 int x, int y, int face,
                 boundaryMode = cudaBoundaryModeTrap);
template<class T>
void surfCubemapread(T data,
                 cudaSurfaceObject_t surfObj,
                 int x, int y, int face,
                 boundaryMode = cudaBoundaryModeTrap);

使用字节坐标x和y以及面索引face读取由cubemap曲面对象surfObj指定的CUDA数组。

7.9.1.12. surfCubemapwrite()

template<class T>
void surfCubemapwrite(T data,
                 cudaSurfaceObject_t surfObj,
                 int x, int y, int face,
                 boundaryMode = cudaBoundaryModeTrap);

将值数据写入CUDA数组,该数组由立方体图对象surfObj在字节坐标x和y以及面索引face处指定。

7.9.1.13. surfCubemapLayeredread()

template<class T>
T surfCubemapLayeredread(
             cudaSurfaceObject_t surfObj,
             int x, int y, int layerFace,
             boundaryMode = cudaBoundaryModeTrap);
template<class T>
void surfCubemapLayeredread(T data,
             cudaSurfaceObject_t surfObj,
             int x, int y, int layerFace,

使用字节坐标x和y以及索引layerFace读取立方体图分层曲面对象surfObj指定的CUDA数组。

7.9.1.14. surfCubemapLayeredwrite()

template<class T>
void surfCubemapLayeredwrite(T data,
             cudaSurfaceObject_t surfObj,
             int x, int y, int layerFace,
             boundaryMode = cudaBoundaryModeTrap);

将值数据写入CUDA数组,该数组由字节坐标x和y处的立方体图分层对象surfObj以及索引layerFace指定。

7.10. Read-Only Data Cache Load Function

只读数据缓存加载功能仅受计算能力为5.0及更高版本的设备支持。

T __ldg(const T* address);

返回位于地址T的类型address的数据,其中Tcharsigned charshortintlonglong longunsigned charunsigned shortunsigned intunsigned longunsigned long longchar2char4short2short4int2int4longlong2uchar2uchar4ushort2ushort4uint2uint4ulonglong2floatfloat2float4doubledouble2。在包含cuda_fp16.h头的情况下,T可以是__half__half2。类似地,在包括cuda_bf16.h报头的情况下,T也可以是__nv_bfloat16__nv_bfloat162。该操作缓存在只读数据缓存中(请参见全局内存)。

7.11. Load Functions Using Cache Hints

只有计算能力为5.0或更高的设备才支持这些负载函数。

T __ldcg(const T* address);
T __ldca(const T* address);
T __ldcs(const T* address);
T __ldlu(const T* address);
T __ldcv(const T* address);

返回位于地址T的类型address的数据,其中Tcharsigned charshortintlonglong longunsigned charunsigned shortunsigned intunsigned longunsigned long longchar2char4short2short4int2int4longlong2uchar2uchar4ushort2ushort4uint2uint4ulonglong2floatfloat2float4doubledouble2。在包含cuda_fp16.h头的情况下,T可以是__half__half2。类似地,在包括cuda_bf16.h报头的情况下,T也可以是__nv_bfloat16__nv_bfloat162。该操作正在使用相应的缓存操作符(参见PTX伊萨

7.12. Store Functions Using Cache Hints

只有计算能力为5.0或更高的设备才支持这些存储功能。

void __stwb(T* address, T value);
void __stcg(T* address, T value);
void __stcs(T* address, T value);
void __stwt(T* address, T value);

将类型valueT变元存储到地址address处的位置,其中Tcharsigned charshortintlonglong longunsigned charunsigned shortunsigned intunsigned longunsigned long longchar2char4short2short4int2int4longlong2uchar2uchar4ushort2ushort4uint2uint4ulonglong2floatfloat2float4doubledouble2。在包含cuda_fp16.h头的情况下,T可以是__half__half2。类似地,在包括cuda_bf16.h报头的情况下,T也可以是__nv_bfloat16__nv_bfloat162。该操作正在使用相应的缓存操作符(参见PTX伊萨

7.13. Time Function

clock_t clock();
long long int clock64();

当在设备代码中执行时,返回每个时钟周期递增的每个多处理器计数器的值。在内核的开始和结束时对该计数器进行采样,取两个采样的差,并且记录每个线程的结果,为每个线程提供了设备完全执行该线程所花费的时钟周期数的度量,而不是设备实际花费在执行线程指令上的时钟周期数的度量。前者的数目大于后者,因为线程是时间分片的。

7.14. Atomic Functions

原子功能对驻留在全局或共享存储器中的一个32位或64位字执行读-修改-写原子操作。在float2float4的情况下,对驻留在全局存储器中的向量的每个元素执行读取-修改-写入操作。例如,atomicAdd()读取全局或共享内存中某个地址的一个字,向其添加一个数字,并将结果写回同一地址。原子函数只能在设备函数中使用。

本节中描述的原子函数具有排序 cuda::内存顺序松弛 并且仅在特定的 范围

  • Atomic APIs with _system suffix (example: __atomicAdd_system) are atomic at scope cuda::thread_scope_system.
    _system后缀的原子API(例如:__atomicAdd_system)在作用域cuda::thread_scope_system中是原子的。

  • Atomic APIs without a suffix (example: __atomicAdd) are atomic at scope cuda::thread_scope_device.
    不带后缀的原子API(例如:__atomicAdd)在作用域cuda::thread_scope_device中是原子的。

  • Atomic APIs with _block suffix (example: __atomicAdd_block) are atomic at scope cuda::thread_scope_block.
    _block后缀的原子API(例如:__atomicAdd_block)在作用域cuda::thread_scope_block中是原子的。

在以下示例中,CPU和GPU都原子地更新地址addr处的整数值:

__global__ void mykernel(int *addr) {
  atomicAdd_system(addr, 10);       // only available on devices with compute capability 6.x
}

void foo() {
  int *addr;
  cudaMallocManaged(&addr, 4);
  *addr = 0;

   mykernel<<<...>>>(addr);
   __sync_fetch_and_add(addr, 10);  // CPU atomic operation
}

注意,任何原子操作都可以基于atomicCAS()(比较和交换)来实现。例如,用于双精度浮点数的atomicAdd()在计算能力低于6.0的设备上不可用,但可以如下实现:

#if __CUDA_ARCH__ < 600
__device__ double atomicAdd(double* address, double val)
{
    unsigned long long int* address_as_ull =
                              (unsigned long long int*)address;
    unsigned long long int old = *address_as_ull, assumed;

    do {
        assumed = old;
        old = atomicCAS(address_as_ull, assumed,
                        __double_as_longlong(val +
                               __longlong_as_double(assumed)));

    // Note: uses integer comparison to avoid hang in case of NaN (since NaN != NaN)
    } while (assumed != old);

    return __longlong_as_double(old);
}
#endif

以下设备范围的原子API有系统范围和块范围的变体,但有以下例外:

  • Devices with compute capability less than 6.0 only support device-wide atomic operations,
    计算能力低于6.0的设备仅支持设备范围的原子操作,

  • Tegra devices with compute capability less than 7.2 do not support system-wide atomic operations.
    计算能力低于7.2的Tegra设备不支持系统范围的原子操作。

7.14.1. Arithmetic Functions

7.14.1.1. atomicAdd()

int atomicAdd(int* address, int val);
unsigned int atomicAdd(unsigned int* address,
                       unsigned int val);
unsigned long long int atomicAdd(unsigned long long int* address,
                                 unsigned long long int val);
float atomicAdd(float* address, float val);
double atomicAdd(double* address, double val);
__half2 atomicAdd(__half2 *address, __half2 val);
__half atomicAdd(__half *address, __half val);
__nv_bfloat162 atomicAdd(__nv_bfloat162 *address, __nv_bfloat162 val);
__nv_bfloat16 atomicAdd(__nv_bfloat16 *address, __nv_bfloat16 val);
float2 atomicAdd(float2* address, float2 val);
float4 atomicAdd(float4* address, float4 val);

读取位于全局或共享存储器中的地址old处的16位、32位或64位address,计算(old + val),并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回old

只有计算能力为2.x及更高版本的设备才支持atomicAdd()的32位浮点版本。

只有计算能力为6.x及更高版本的设备才支持atomicAdd()的64位浮点版本。

__half2的32位atomicAdd()浮点版本仅受计算能力为6.x及更高版本的设备支持。对于两个__half2__nv_bfloat162元素中的每一个,分别保证__half__nv_bfloat16加法操作的原子性;不能保证整个__half2__nv_bfloat162作为单个32位访问是原子的。

float2float4atomicAdd()浮点向量版本仅受计算能力为9.x及更高版本的设备支持。对于两个或四个float2元素中的每一个,分别保证float4float加法操作的原子性;不能保证整个float2float4作为单个64位或128位访问是原子的。

__half的16位atomicAdd()浮点版本仅受计算能力为7.x及更高版本的设备支持。

__nv_bfloat16的16位atomicAdd()浮点版本仅受计算能力为8.x及更高版本的设备支持。

float2float4atomicAdd()浮点向量版本仅受计算能力为9.x及更高版本的设备支持。

float2float4atomicAdd()浮点向量版本仅支持全局内存地址。

7.14.1.2. atomicSub()

int atomicSub(int* address, int val);
unsigned int atomicSub(unsigned int* address,
                       unsigned int val);

读取位于全局或共享存储器中的地址old处的32位字address,计算(old - val),并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回old

7.14.1.3. atomicExch()

int atomicExch(int* address, int val);
unsigned int atomicExch(unsigned int* address,
                        unsigned int val);
unsigned long long int atomicExch(unsigned long long int* address,
                                  unsigned long long int val);
float atomicExch(float* address, float val);

读取位于全局或共享存储器中的地址old处的32位或64位字address,并将val存储回相同地址处的存储器。这两个操作在一个原子事务中执行。函数返回old

7.14.1.4. atomicMin()

int atomicMin(int* address, int val);
unsigned int atomicMin(unsigned int* address,
                       unsigned int val);
unsigned long long int atomicMin(unsigned long long int* address,
                                 unsigned long long int val);
long long int atomicMin(long long int* address,
                                long long int val);

读取位于全局或共享存储器中的地址old处的32位或64位字address,计算oldval的最小值,并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回old

只有计算能力为5.0及更高版本的设备才支持64位版本的atomicMin()

7.14.1.5. atomicMax()

int atomicMax(int* address, int val);
unsigned int atomicMax(unsigned int* address,
                       unsigned int val);
unsigned long long int atomicMax(unsigned long long int* address,
                                 unsigned long long int val);
long long int atomicMax(long long int* address,
                                 long long int val);

读取位于全局或共享存储器中的地址old处的32位或64位字address,计算oldval的最大值,并将结果存储回存储器的相同地址。这三个操作在一个原子事务中执行。函数返回old

7.14.1.6. atomicInc()

unsigned int atomicInc(unsigned int* address,
                       unsigned int val);

读取位于全局或共享存储器中的地址old处的32位字address,计算((old >= val) ? 0 : (old+1)),并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回old

7.14.1.7. atomicDec()

unsigned int atomicDec(unsigned int* address,
                       unsigned int val);

读取位于全局或共享存储器中的地址old处的32位字address,计算(((old == 0) || (old > val)) ? val : (old-1)),并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回old

7.14.1.8. atomicCAS()

int atomicCAS(int* address, int compare, int val);
unsigned int atomicCAS(unsigned int* address,
                       unsigned int compare,
                       unsigned int val);
unsigned long long int atomicCAS(unsigned long long int* address,
                                 unsigned long long int compare,
                                 unsigned long long int val);
unsigned short int atomicCAS(unsigned short int *address,
                             unsigned short int compare,
                             unsigned short int val);

读取位于全局或共享存储器中的地址old处的16位、32位或64位字address,计算(old == compare ? val : old),并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回old(比较和交换)。

7.14.2. Bitwise Functions

7.14.2.1. atomicAnd()

int atomicAnd(int* address, int val);
unsigned int atomicAnd(unsigned int* address,
                       unsigned int val);
unsigned long long int atomicAnd(unsigned long long int* address,
                                 unsigned long long int val);

读取位于全局或共享存储器中的地址old处的32位或64位字address,计算(old & val),并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回old

7.14.2.2. atomicOr()

int atomicOr(int* address, int val);
unsigned int atomicOr(unsigned int* address,
                      unsigned int val);
unsigned long long int atomicOr(unsigned long long int* address,
                                unsigned long long int val);

读取位于全局或共享存储器中的地址old处的32位或64位字address,计算(old | val),并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回old

7.14.2.3. atomicXor()

int atomicXor(int* address, int val);
unsigned int atomicXor(unsigned int* address,
                       unsigned int val);
unsigned long long int atomicXor(unsigned long long int* address,
                                 unsigned long long int val);

读取位于全局或共享存储器中的地址old处的32位或64位字address,计算(old ^ val),并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回old

7.15. Address Space Predicate Functions

7.15.1. __isGlobal()__

__device__ unsigned int __isGlobal(const void *ptr);

如果ptr包含全局内存空间中对象的通用地址,则返回1,否则返回0。

7.15.2. __isShared()__

__device__ unsigned int __isShared(const void *ptr);

如果ptr包含共享内存空间中对象的通用地址,则返回1,否则返回0。

7.15.3. __isConstant()__

__device__ unsigned int __isConstant(const void *ptr);

如果ptr包含常量内存空间中对象的泛型地址,则返回1,否则返回0

7.15.4. __isGridConstant()

__device__ unsigned int __isGridConstant(const void *ptr);

如果ptr包含用__grid_constant__注释的内核参数的泛型地址,则返回1,否则返回0。仅支持高于或等于7.x或更高版本的计算体系结构。

7.15.5. __isLocal()

__device__ unsigned int __isLocal(const void *ptr);

如果ptr包含对象在本地内存空间中的通用地址,则返回1,否则返回0。

7.16. Address Space Conversion Functions

7.16.1. __cvta_generic_to_global()__

__device__ size_t __cvta_generic_to_global(const void *ptr);

返回在由cvta.to.local表示的通用地址上执行PTXptr指令的结果。

7.16.2. __cvta_generic_to_shared()__

__device__ size_t __cvta_generic_to_shared(const void *ptr);

返回在由cvta.to.local表示的通用地址上执行PTXptr指令的结果。

7.16.3. __cvta_generic_to_constant()__

__device__ size_t __cvta_generic_to_constant(const void *ptr);

返回在由cvta.to.local表示的通用地址上执行PTXptr指令的结果。

7.16.4. __cvta_generic_to_local()__

__device__ size_t __cvta_generic_to_local(const void *ptr);

返回在由cvta.to.local表示的通用地址上执行PTXptr指令的结果。

7.16.5. __cvta_global_to_generic()__

__device__ void * __cvta_global_to_generic(size_t rawbits);

返回通过对cvta.global提供的值执行PTXrawbits指令获得的通用指针

7.16.6. __cvta_shared_to_generic()__

__device__ void * __cvta_shared_to_generic(size_t rawbits);

返回通过对cvta.shared提供的值执行PTXrawbits指令获得的通用指针。

7.16.7. __cvta_constant_to_generic()__

__device__ void * __cvta_constant_to_generic(size_t rawbits);

返回通过对cvta.const提供的值执行PTXrawbits指令获得的通用指针。

7.16.8. __cvta_local_to_generic()__

__device__ void * __cvta_local_to_generic(size_t rawbits);

返回通过对cvta.local提供的值执行PTXrawbits指令获得的通用指针。

7.17. Alloca Function

7.17.1. Synopsis

__host__ __device__ void * alloca(size_t size);

7.17.2. Description

alloca()函数在调用者的堆栈帧中分配size字节的内存。返回值是指向已分配内存的指针,当从设备代码调用函数时,内存的开头是16字节对齐的。当alloca()的调用者返回时,分配的内存会自动释放。

7.17.3. Example

__device__ void foo(unsigned int num) {
    int4 *ptr = (int4 *)alloca(num * sizeof(int4));
    // use of ptr
    ...
}

7.18. Compiler Optimization Hint Functions

本节介绍的函数可用于向编译器优化器提供附加信息。

7.18.1. __builtin_assume_aligned()__

void * __builtin_assume_aligned (const void *exp, size_t align)

允许编译器假定参数指针至少与align字节对齐,并返回参数指针。

void *res = __builtin_assume_aligned(ptr, 32); // compiler can assume 'res' is
                                               // at least 32-byte aligned

Three parameter version:

void * __builtin_assume_aligned (const void *exp, size_t align,
                                 <integral type> offset)

允许编译器假设(char *)exp - offset至少与align字节对齐,并返回参数指针

void *res = __builtin_assume_aligned(ptr, 32, 8); // compiler can assume
                                                  // '(char *)res - 8' is
                                                  // at least 32-byte aligned.

7.18.2. __builtin_assume()__

void __builtin_assume(bool exp)

允许编译器假定布尔参数为true。如果该参数在运行时不为真,则该行为未定义。注意,如果参数有副作用,则行为是未指定的。

__device__ int get(int *ptr, int idx) {
   __builtin_assume(idx <= 2);
   return ptr[idx];
}

7.18.3. __assume()__

void __assume(bool exp)

允许编译器假定布尔参数为true。如果该参数在运行时不为真,则该行为未定义。注意,如果参数有副作用,则行为是未指定的。

 __device__ int get(int *ptr, int idx) {
   __assume(idx <= 2);
   return ptr[idx];
}

7.18.4. __builtin_expect()__

long __builtin_expect (long exp, long c)

向编译器指示预期的exp == c,并返回exp的值。通常用于向编译器指示分支预测信息。

// indicate to the compiler that likely "var == 0",
// so the body of the if-block is unlikely to be
// executed at run time.
if (__builtin_expect (var, 0))
  doit ();

7.18.5. __builtin_unreachable()__

void __builtin_unreachable(void)

向编译器指示控制流永远不会到达从中调用此函数的点。如果控制流在运行时确实到达此点,则程序具有未定义的行为。

// indicates to the compiler that the default case label is never reached.
switch (in) {
case 1: return 4;
case 2: return 10;
default: __builtin_unreachable();
}

7.18.6. Restrictions

仅在使用__assume()主机编译器时支持cl.exe。其他功能在所有平台上均受支持,但有以下限制:

  • 如果宿主编译器支持该函数,则可以从翻译单元中的任何位置调用该函数。

  • 否则,该函数必须从__device__函数体中调用,或者仅在定义了__global__宏时调用12

7.19. Warp Vote Functions

int __all_sync(unsigned mask, int predicate);
int __any_sync(unsigned mask, int predicate);
unsigned __ballot_sync(unsigned mask, int predicate);
unsigned __activemask();

7.20. Warp Match Functions

__match_any_sync__match_all_sync执行变量的广播和比较操作

7.20.1. Synopsis

unsigned int __match_any_sync(unsigned mask, T value);
unsigned int __match_all_sync(unsigned mask, T value, int *pred);

T可以是intunsigned intlongunsigned longlong longunsigned long longfloatdouble

7.20.2. Description

__match_sync() intrinsic允许在同步value中命名的线程之后,跨线程束中的线程广播和比较值mask

__match_any_sync
返回在value中具有相同值mask的线程的掩码

__match_all_sync

如果mask中所有线程的mask值相同,则返回value;否则返回0。如果pred中的所有线程具有相同的值mask,则谓词value被设置为真;否则将谓词设置为假。

新的*_sync match intrinsic接受一个掩码,指示参与调用的线程。必须为每个参与线程设置一个表示线程通道ID的位,以确保它们在硬件执行内在函数之前正确收敛。每个调用线程必须在掩码中设置自己的位,并且所有在掩码中命名的未退出线程必须使用相同的掩码执行相同的内在函数,否则结果是未定义的。

这些内部函数并不意味着内存障碍。它们不保证任何内存顺序。

7.27. Asynchronous Data Copies

CUDA 11通过memcpy_async API引入了异步数据操作,允许设备代码显式管理数据的异步复制memcpy_async特性使CUDA内核能够将计算与数据移动重叠。

7.27.1. memcpy_async API

memcpy_async API在cuda/barriercuda/pipelinecooperative_groups/memcpy_async.h头文件中提供。

cuda::memcpy_async API使用cuda::barriercuda::pipeline同步原语,而cooperative_groups::memcpy_async使用coopertive_groups::wait同步。

这些API具有非常相似的语义:将对象从src复制到dst,就像由另一个线程执行一样,在复制完成时,可以通过cuda::pipelinecuda::barriercooperative_groups::wait同步。

提供了用于cuda::memcpy_asynccuda::barriercuda::pipeline重载的完整API文档 libcudacxx API文档沿着一些示例。

cooperative_groups::memcpy_async在文档的cooperative_groups::memcpy_async cooperative groupscooperative_groups::memcpy_async协作组部分中提供。

使用 cuda::barrier cuda::barriercuda::barriermemcpy_asynccuda::pipeline API需要7.0或更高的计算能力。在具有8.0或更高计算能力的设备上,从全局到共享内存的memcpy_async操作可以受益于硬件加速。

7.27.2. Copy and Compute Pattern - Staging Data Through Shared Memory

CUDA应用程序通常采用复制和计算模式,该模式:

  • fetches data from global memory,从全局存储器获取数据

  • stores data to shared memory, and将数据存储到共享内存,以及

  • performs computations on shared memory data, and potentially writes results back to global memory.
    对共享存储器数据执行计算并可能将结果写回到全局存储器。

以下各节说明了如何在不使用和使用memcpy_async功能的情况下表达此模式:

7.27.3. Without memcpy_async

在没有memcpy_async的情况下,复制和计算模式的复制阶段被表示为shared[local_idx] = global[global_idx]。这种全局到共享内存的复制扩展为从全局内存读取寄存器,然后从寄存器写入共享内存

当这种模式出现在迭代算法中时,每个线程块都需要在shared[local_idx]=global[global_idx]配之后同步,以确保在计算阶段开始之前完成对共享内存的所有写入。线程块还需要在计算阶段之后再次同步,以防止在所有线程完成计算之前覆盖共享内存。下面的代码片段演示了此模式。

#include <cooperative_groups.h>
__device__ void compute(int* global_out, int const* shared_in) {
    // Computes using all values of current batch from shared memory.
    // Stores this thread's result back to global memory.
}

__global__ void without_memcpy_async(int* global_out, int const* global_in, size_t size, size_t batch_sz) {
  auto grid = cooperative_groups::this_grid();
  auto block = cooperative_groups::this_thread_block();
  assert(size == batch_sz * grid.size()); // Exposition: input size fits batch_sz * grid_size

  extern __shared__ int shared[]; // block.size() * sizeof(int) bytes

  size_t local_idx = block.thread_rank();

  for (size_t batch = 0; batch < batch_sz; ++batch) {
    // Compute the index of the current batch for this block in global memory:
    size_t block_batch_idx = block.group_index().x * block.size() + grid.size() * batch;
    size_t global_idx = block_batch_idx + threadIdx.x;
    shared[local_idx] = global_in[global_idx];

    block.sync(); // Wait for all copies to complete

    compute(global_out + block_batch_idx, shared); // Compute and write result to global memory

    block.sync(); // Wait for compute using shared memory to finish
  }
}

7.27.4. With memcpy_async

使用memcpy_async,从全局内存分配共享内存

shared[local_idx] = global_in[global_idx];

被替换为异步复制操作cooperative groups

cooperative_groups::memcpy_async(group, shared, global_in + batch_idx, sizeof(int) * block.size());

 cooperative_groups::memcpy_asyncAPI从全局内存中从sizeof(int) * block.size()开始复制global_in + batch_idx字节到shared数据。此操作就像是由另一个线程执行的一样,在复制完成后,该线程与当前线程对cooperative_groups::wait在复制操作完成之前,修改全局数据或阅读或写入共享数据会引入数据争用。

在具有8.0或更高计算能力的设备上,从全局存储器到共享存储器的memcpy_async传输可以受益于硬件加速,这避免了通过中间寄存器传输数据

#include <cooperative_groups.h>
#include <cooperative_groups/memcpy_async.h>

__device__ void compute(int* global_out, int const* shared_in);

__global__ void with_memcpy_async(int* global_out, int const* global_in, size_t size, size_t batch_sz) {
  auto grid = cooperative_groups::this_grid();
  auto block = cooperative_groups::this_thread_block();
  assert(size == batch_sz * grid.size()); // Exposition: input size fits batch_sz * grid_size

  extern __shared__ int shared[]; // block.size() * sizeof(int) bytes

  for (size_t batch = 0; batch < batch_sz; ++batch) {
    size_t block_batch_idx = block.group_index().x * block.size() + grid.size() * batch;
    // Whole thread-group cooperatively copies whole batch to shared memory:
    cooperative_groups::memcpy_async(block, shared, global_in + block_batch_idx, sizeof(int) * block.size());

    cooperative_groups::wait(block); // Joins all threads, waits for all copies to complete

    compute(global_out + block_batch_idx, shared);

    block.sync();
  }
}}

7.27.5. Asynchronous Data Copies using cuda::barrier

 cuda::barriercuda::memcpy_async重载允许使用barrier同步异步数据传输。此重载通过以下方式执行复制操作,就像由绑定到屏障的另一个线程执行一样:在创建时递增当前阶段的预期计数,并且在复制操作完成时递减它,使得barrier的阶段将仅在参与屏障的所有线程已经到达并且绑定到屏障的当前阶段的所有memcpy_async已经完成时前进。以下示例使用块范围的barrier,其中所有块线程都参与,并将等待操作与屏障arrive_and_wait交换,同时提供与前一示例相同的功能:

#include <cooperative_groups.h>
#include <cuda/barrier>
__device__ void compute(int* global_out, int const* shared_in);

__global__ void with_barrier(int* global_out, int const* global_in, size_t size, size_t batch_sz) {
  auto grid = cooperative_groups::this_grid();
  auto block = cooperative_groups::this_thread_block();
  assert(size == batch_sz * grid.size()); // Assume input size fits batch_sz * grid_size

  extern __shared__ int shared[]; // block.size() * sizeof(int) bytes

  // Create a synchronization object (C++20 barrier)
  __shared__ cuda::barrier<cuda::thread_scope::thread_scope_block> barrier;
  if (block.thread_rank() == 0) {
    init(&barrier, block.size()); // Friend function initializes barrier
  }
  block.sync();

  for (size_t batch = 0; batch < batch_sz; ++batch) {
    size_t block_batch_idx = block.group_index().x * block.size() + grid.size() * batch;
    cuda::memcpy_async(block, shared, global_in + block_batch_idx, sizeof(int) * block.size(), barrier);

    barrier.arrive_and_wait(); // Waits for all copies to complete

    compute(global_out + block_batch_idx, shared);

    block.sync();
  }
}

7.27.6. Performance Guidance for memcpy_async

对于计算能力8.x,流水线机制在相同CUDA扭曲中的CUDA线程之间共享。这种共享导致memcpy_async的批次在经线内缠结,这在某些情况下会影响性能。

本节重点介绍warp纠缠对提交、等待和到达操作的影响。有关各个操作的概述,请参阅管道接口管道原语接口

7.27.6.1. Alignment

在计算能力为8.0的设备上, cp.async family of instructions 允许将数据从全局内存异步复制到共享内存。这些指令支持一次复制4、8及16个字节。如果提供给memcpy_async的大小是4、8或16的倍数,并且传递给memcpy_async的两个指针都与4、8或16的对齐边界对齐,则可以使用排他异步存储器操作来实现memcpy_async

此外,为了在使用memcpy_async API时实现最佳性能,需要对共享内存和全局内存进行128字节的对齐。

对于指向对齐要求为1或2的类型的值的指针,通常无法证明指针始终与更高的对齐边界对齐。确定是否可以使用cp.async指令必须延迟到运行时。执行这样的运行时对齐检查会增加代码大小并增加运行时开销。

cuda::aligned_size_t<size_t Align>(size_t size)Shape 可以用来证明传递给memcpy_async的两个指针都与Align对齐边界对齐,并且sizeAlign的倍数,通过将其作为参数传递,其中memcpy_async API期望Shape

cuda::memcpy_async(group, dst, src, cuda::aligned_size_t<16>(N * block.size()), pipeline);

7.27.6.2. Trivially copyable

在计算能力为8.0的设备上, cp.async family of instructions 允许将数据从全局内存异步复制到共享内存。如果传递给memcpy_async的指针类型没有指向TriviallyCopyable类型,则需要调用每个输出元素的复制构造函数,这些指令不能用于加速memcpy_async

7.27.6.3. Warp Entanglement - Commit

memcpy_async批次的序列在整个线程束中共享。合并提交操作,使得序列对于调用提交操作的所有收敛线程递增一次。如果扭曲完全收敛,则序列递增1;如果翘曲完全发散,则序列递增32。

例如,当扭曲完全发散时:

  • 令PB为warp-shared流水线的实际批处理序列。

    PB = {BP0, BP1, BP2, …, BPL}

  • 假设TB是线程感知到的批处理序列,就好像该序列仅通过该线程调用提交操作而递增。

    TB = {BT0, BT1, BT2, …, BTL}

    pipeline::producer_commit()返回值来自线程感知的批处理序列。

  • 线程感知序列中的索引总是与实际warp共享序列中相等或更大的索引对齐。仅当所有提交操作都是从收敛线程调用时,序列才相等。

    BTn ≡ BPm其中n <= m

warp-shared管道的实际顺序为:PB = {0, 1, 2, 3, ..., 31}PL=31)。

这条经线的每一根线的感知顺序是:

  • 线程0:TB = {0}TL=0

  • 线程1:TB = {0}TL=0

  • 线程31:TB = {0}TL=0

7.27.6.4. Warp Entanglement - Wait

CUDA线程调用pipeline_consumer_wait_prior<N>()pipeline::consumer_wait()以等待感知序列TB中的批处理完成。注意pipeline::consumer_wait()等同于pipeline_consumer_wait_prior<N>(),其中N=PL

pipeline_consumer_wait_prior<N>()函数等待实际序列中的批次,至少直到并包括PL-N。从TL <= PL开始,等待批次直到并包括PL-N包括等待批次TL-N。因此,当TL < PL时,线程将无意中等待额外的、更近的批处理。

在上面极端的完全发散的warp示例中,每个线程可以等待所有32个批次。

7.27.6.5. Warp Entanglement - Arrive-On

Warp-divergence affects the number of times an arrive_on(bar) 。如果调用的扭曲完全收敛,那么屏障更新一次。如果调用扭曲完全发散,则将32个单独的更新应用于屏障。

7.27.6.6. Keep Commit and Arrive-On Operations Converged

建议由收敛线程执行提交和到达调用:

  • to not over-wait, by keeping threads’ perceived sequence of batches aligned with the actual sequence, and
    通过保持线程感知的批处理序列与实际序列对齐,不过度等待,以及

  • to minimize updates to the barrier object.
    以最小化对栅栏对象的更新。

When code preceding these operations diverges threads, then the warp should be re-converged, via __syncwarp before invoking commit or arrive-on operations.
当这些操作之前的代码使线程发散时,则线程束应该在调用提交或到达操作之前经由__syncwarp重新收敛。

7.28. Asynchronous Data Copies using cuda::pipeline

CUDA提供了cuda::pipeline同步对象来管理异步数据移动并将其与计算重叠。

cuda::pipeline的API文档在 libcudacxx API。流水线对象是具有头部和尾部的双端N级队列,并且用于以先进先出(FIFO)顺序处理工作。管道对象具有以下成员函数来管理管道的阶段。

Pipeline Class Member Function管道类成员函数

Description说明

producer_acquire

 

cudaStreamGraphFireAndForget

Acquires an available stage in the pipeline internal queue.
获取管道内部队列中的可用阶段。

producer_commit

 

cudaStreamGraphFireAndForget

Commits the asynchronous operations issued after the producer_acquire call on the currently acquired stage of the pipeline.
提交在当前获取的管道阶段上执行producer_acquire调用之后发出的异步操作。

consumer_wait

 

cudaStreamGraphFireAndForget

Wait for completion of all asynchronous operations on the oldest stage of the pipeline.
等待管道最旧阶段上的所有异步操作完成。

consumer_release

 

cudaStreamGraphFireAndForget

Release the oldest stage of the pipeline to the pipeline object for reuse. The released stage can be then acquired by the producer.
将管道的最旧阶段释放给管道对象以供重用。然后,生产商可以获得释放的阶段。

7.28.1. Single-Stage Asynchronous Data Copies using cuda::pipeline

在前面的例子中,我们展示了如何使用cooperative_groupscuda::barrier进行异步数据传输。在本节中,我们将使用带有单个阶段的cuda::pipeline API来调度异步拷贝。稍后,我们将扩展此示例以显示多阶段重叠计算和拷贝。

#include <cooperative_groups/memcpy_async.h>
#include <cuda/pipeline>

__device__ void compute(int* global_out, int const* shared_in);
__global__ void with_single_stage(int* global_out, int const* global_in, size_t size, size_t batch_sz) {
    auto grid = cooperative_groups::this_grid();
    auto block = cooperative_groups::this_thread_block();
    assert(size == batch_sz * grid.size()); // Assume input size fits batch_sz * grid_size

    constexpr size_t stages_count = 1; // Pipeline with one stage
    // One batch must fit in shared memory:
    extern __shared__ int shared[];  // block.size() * sizeof(int) bytes

    // Allocate shared storage for a two-stage cuda::pipeline:
    __shared__ cuda::pipeline_shared_state<
        cuda::thread_scope::thread_scope_block,
        stages_count
    > shared_state;
    auto pipeline = cuda::make_pipeline(block, &shared_state);

    // Each thread processes `batch_sz` elements.
    // Compute offset of the batch `batch` of this thread block in global memory:
    auto block_batch = [&](size_t batch) -> int {
      return block.group_index().x * block.size() + grid.size() * batch;
    };

    for (size_t batch = 0; batch < batch_sz; ++batch) {
        size_t global_idx = block_batch(batch);

        // Collectively acquire the pipeline head stage from all producer threads:
        pipeline.producer_acquire();

        // Submit async copies to the pipeline's head stage to be
        // computed in the next loop iteration
        cuda::memcpy_async(block, shared, global_in + global_idx, sizeof(int) * block.size(), pipeline);
        // Collectively commit (advance) the pipeline's head stage
        pipeline.producer_commit();

        // Collectively wait for the operations committed to the
        // previous `compute` stage to complete:
        pipeline.consumer_wait();

        // Computation overlapped with the memcpy_async of the "copy" stage:
        compute(global_out + global_idx, shared);

        // Collectively release the stage resources
        pipeline.consumer_release();
    }
}

7.28.2. Multi-Stage Asynchronous Data Copies using  cuda::pipeline

在前面使用 cooperative_groups::waitcuda::barrier的示例中,内核线程立即等待到共享内存的数据传输完成。这避免了数据从全局存储器传输到寄存器,但不会通过重叠计算隐藏memcpy_async操作的延迟。

为此,我们使用CUDA 管线下例中的功能。它提供了一种管理memcpy_async批处理序列的机制,使CUDA内核能够将内存传输与计算重叠。下面的示例实现了一个两阶段的管道,该管道将数据传输与计算重叠。它:

  • 初始化管道共享状态(详细信息如下)

  • 通过为第一个批次安排memcpy_async来启动管道。

  • 在所有批次上循环:它为下一批调度memcpy_async,在完成前一批的memcpy_async时阻塞所有线程,然后将前一批的计算与下一批的存储器的异步副本重叠。

  • 最后,它通过在最后一个批处理上执行计算来排空管道。

注意,为了与cuda::pipeline的互操作性,这里使用来自cuda::memcpy_async报头的cuda/pipeline

#include <cooperative_groups/memcpy_async.h>
#include <cuda/pipeline>

__device__ void compute(int* global_out, int const* shared_in);
__global__ void with_staging(int* global_out, int const* global_in, size_t size, size_t batch_sz) {
    auto grid = cooperative_groups::this_grid();
    auto block = cooperative_groups::this_thread_block();
    assert(size == batch_sz * grid.size()); // Assume input size fits batch_sz * grid_size

    constexpr size_t stages_count = 2; // Pipeline with two stages
    // Two batches must fit in shared memory:
    extern __shared__ int shared[];  // stages_count * block.size() * sizeof(int) bytes
    size_t shared_offset[stages_count] = { 0, block.size() }; // Offsets to each batch

    // Allocate shared storage for a two-stage cuda::pipeline:
    __shared__ cuda::pipeline_shared_state<
        cuda::thread_scope::thread_scope_block,
        stages_count
    > shared_state;
    auto pipeline = cuda::make_pipeline(block, &shared_state);

    // Each thread processes `batch_sz` elements.
    // Compute offset of the batch `batch` of this thread block in global memory:
    auto block_batch = [&](size_t batch) -> int {
      return block.group_index().x * block.size() + grid.size() * batch;
    };

    // Initialize first pipeline stage by submitting a `memcpy_async` to fetch a whole batch for the block:
    if (batch_sz == 0) return;
    pipeline.producer_acquire();
    cuda::memcpy_async(block, shared + shared_offset[0], global_in + block_batch(0), sizeof(int) * block.size(), pipeline);
    pipeline.producer_commit();

    // Pipelined copy/compute:
    for (size_t batch = 1; batch < batch_sz; ++batch) {
        // Stage indices for the compute and copy stages:
        size_t compute_stage_idx = (batch - 1) % 2;
        size_t copy_stage_idx = batch % 2;

        size_t global_idx = block_batch(batch);

        // Collectively acquire the pipeline head stage from all producer threads:
        pipeline.producer_acquire();

        // Submit async copies to the pipeline's head stage to be
        // computed in the next loop iteration
        cuda::memcpy_async(block, shared + shared_offset[copy_stage_idx], global_in + global_idx, sizeof(int) * block.size(), pipeline);
        // Collectively commit (advance) the pipeline's head stage
        pipeline.producer_commit();

        // Collectively wait for the operations commited to the
        // previous `compute` stage to complete:
        pipeline.consumer_wait();

        // Computation overlapped with the memcpy_async of the "copy" stage:
        compute(global_out + global_idx, shared + shared_offset[compute_stage_idx]);

        // Collectively release the stage resources
        pipeline.consumer_release();
    }

    // Compute the data fetch by the last iteration
    pipeline.consumer_wait();
    compute(global_out + block_batch(batch_sz-1), shared + shared_offset[(batch_sz - 1) % 2]);
    pipeline.consumer_release();
}

管道 对象是具有头部和尾部的双端队列,用于按照先进先出(FIFO)顺序处理工作。生产者线程将工作提交到管道的头部,而消费者线程从管道的尾部拉取工作。在上面的示例中,所有线程都是生产者线程和消费者线程。线程首先提交memcpy_async操作以获取下一批,同时等待前一批memcpy_async操作完成。

  • Committing work to a pipeline stage involves:
    将工作提交到管道阶段包括:

    • Collectively acquiring the pipeline head from a set of producer threads using pipeline.producer_acquire().
      使用pipeline.producer_acquire()从一组生产者线程集体获取流水线头。

    • Submitting memcpy_async operations to the pipeline head.
      正在将memcpy_async操作提交到管道头。

    • Collectively commiting (advancing) the pipeline head using pipeline.producer_commit().
      使用pipeline.producer_commit()共同提交(推进)管线头。

  • Using a previously commited stage involves:
    使用先前提交的阶段包括:

    • Collectively waiting for the stage to complete, e.g., using pipeline.consumer_wait() to wait on the tail (oldest) stage.

    • Collectively releasing the stage using pipeline.consumer_release().
      Collectively releasing the stage using pipeline.consumer_release().

cuda::pipeline_shared_state〈scope,count〉封装了允许流水线处理多达count个并发阶段的有限资源。如果所有资源都在使用中,pipeline.producer_acquire()将阻塞生产者线程,直到消费者线程释放下一个管道阶段的资源。

通过将循环的prolog和epilog与循环本身合并,可以更简洁地编写此示例,如下所示:

template <size_t stages_count = 2 /* Pipeline with stages_count stages */>
__global__ void with_staging_unified(int* global_out, int const* global_in, size_t size, size_t batch_sz) {
    auto grid = cooperative_groups::this_grid();
    auto block = cooperative_groups::this_thread_block();
    assert(size == batch_sz * grid.size()); // Assume input size fits batch_sz * grid_size

    extern __shared__ int shared[]; // stages_count * block.size() * sizeof(int) bytes
    size_t shared_offset[stages_count];
    for (int s = 0; s < stages_count; ++s) shared_offset[s] = s * block.size();

    __shared__ cuda::pipeline_shared_state<
        cuda::thread_scope::thread_scope_block,
        stages_count
    > shared_state;
    auto pipeline = cuda::make_pipeline(block, &shared_state);

    auto block_batch = [&](size_t batch) -> int {
        return block.group_index().x * block.size() + grid.size() * batch;
    };

    // compute_batch: next batch to process
    // fetch_batch:  next batch to fetch from global memory
    for (size_t compute_batch = 0, fetch_batch = 0; compute_batch < batch_sz; ++compute_batch) {
        // The outer loop iterates over the computation of the batches
        for (; fetch_batch < batch_sz && fetch_batch < (compute_batch + stages_count); ++fetch_batch) {
            // This inner loop iterates over the memory transfers, making sure that the pipeline is always full
            pipeline.producer_acquire();
            size_t shared_idx = fetch_batch % stages_count;
            size_t batch_idx = fetch_batch;
            size_t block_batch_idx = block_batch(batch_idx);
            cuda::memcpy_async(block, shared + shared_offset[shared_idx], global_in + block_batch_idx, sizeof(int) * block.size(), pipeline);
            pipeline.producer_commit();
        }
        pipeline.consumer_wait();
        int shared_idx = compute_batch % stages_count;
        int batch_idx = compute_batch;
        compute(global_out + block_batch(batch_idx), shared + shared_offset[shared_idx]);
        pipeline.consumer_release();
    }
}

上面使用的pipeline<thread_scope_block>原语非常灵活,并且支持我们上面的示例没有使用的两个特性:块中线程的任意子集可以参与pipeline,并且从参与的线程中,任何子集可以是生产者、消费者或两者。在下面的示例中,线程等级为“even”的线程是生产者,而其他线程是消费者:

__device__ void compute(int* global_out, int shared_in);

template <size_t stages_count = 2>
__global__ void with_specialized_staging_unified(int* global_out, int const* global_in, size_t size, size_t batch_sz) {
    auto grid = cooperative_groups::this_grid();
    auto block = cooperative_groups::this_thread_block();

    // In this example, threads with "even" thread rank are producers, while threads with "odd" thread rank are consumers:
    const cuda::pipeline_role thread_role
      = block.thread_rank() % 2 == 0? cuda::pipeline_role::producer : cuda::pipeline_role::consumer;

    // Each thread block only has half of its threads as producers:
    auto producer_threads = block.size() / 2;

    // Map adjacent even and odd threads to the same id:
    const int thread_idx = block.thread_rank() / 2;

    auto elements_per_batch = size / batch_sz;
    auto elements_per_batch_per_block = elements_per_batch / grid.group_dim().x;

    extern __shared__ int shared[]; // stages_count * elements_per_batch_per_block * sizeof(int) bytes
    size_t shared_offset[stages_count];
    for (int s = 0; s < stages_count; ++s) shared_offset[s] = s * elements_per_batch_per_block;

    __shared__ cuda::pipeline_shared_state<
        cuda::thread_scope::thread_scope_block,
        stages_count
    > shared_state;
    cuda::pipeline pipeline = cuda::make_pipeline(block, &shared_state, thread_role);

    // Each thread block processes `batch_sz` batches.
    // Compute offset of the batch `batch` of this thread block in global memory:
    auto block_batch = [&](size_t batch) -> int {
      return elements_per_batch * batch + elements_per_batch_per_block * blockIdx.x;
    };

    for (size_t compute_batch = 0, fetch_batch = 0; compute_batch < batch_sz; ++compute_batch) {
        // The outer loop iterates over the computation of the batches
        for (; fetch_batch < batch_sz && fetch_batch < (compute_batch + stages_count); ++fetch_batch) {
            // This inner loop iterates over the memory transfers, making sure that the pipeline is always full
            if (thread_role == cuda::pipeline_role::producer) {
                // Only the producer threads schedule asynchronous memcpys:
                pipeline.producer_acquire();
                size_t shared_idx = fetch_batch % stages_count;
                size_t batch_idx = fetch_batch;
                size_t global_batch_idx = block_batch(batch_idx) + thread_idx;
                size_t shared_batch_idx = shared_offset[shared_idx] + thread_idx;
                cuda::memcpy_async(shared + shared_batch_idx, global_in + global_batch_idx, sizeof(int), pipeline);
                pipeline.producer_commit();
            }
        }
        if (thread_role == cuda::pipeline_role::consumer) {
            // Only the consumer threads compute:
            pipeline.consumer_wait();
            size_t shared_idx = compute_batch % stages_count;
            size_t global_batch_idx = block_batch(compute_batch) + thread_idx;
            size_t shared_batch_idx = shared_offset[shared_idx] + thread_idx;
            compute(global_out + global_batch_idx, *(shared + shared_batch_idx));
            pipeline.consumer_release();
        }
    }
}

管道执行了一些优化,例如,当所有线程都是生产者和消费者时,但一般来说,支持所有这些特性的成本不能完全消除。例如,管道在共享内存中存储并使用一组屏障进行同步,如果块中的所有线程都参与管道,则实际上并不需要这样做。

对于块中的所有线程都参与pipeline的特定情况,我们可以通过使用pipeline<thread_scope_block>pipeline<thread_scope_thread>的组合来做得比__syncthreads()更好:

template<size_t stages_count>
__global__ void with_staging_scope_thread(int* global_out, int const* global_in, size_t size, size_t batch_sz) {
    auto grid = cooperative_groups::this_grid();
    auto block = cooperative_groups::this_thread_block();
    auto thread = cooperative_groups::this_thread();
    assert(size == batch_sz * grid.size()); // Assume input size fits batch_sz * grid_size

    extern __shared__ int shared[]; // stages_count * block.size() * sizeof(int) bytes
    size_t shared_offset[stages_count];
    for (int s = 0; s < stages_count; ++s) shared_offset[s] = s * block.size();

    // No pipeline::shared_state needed
    cuda::pipeline<cuda::thread_scope_thread> pipeline = cuda::make_pipeline();

    auto block_batch = [&](size_t batch) -> int {
        return block.group_index().x * block.size() + grid.size() * batch;
    };

    for (size_t compute_batch = 0, fetch_batch = 0; compute_batch < batch_sz; ++compute_batch) {
        for (; fetch_batch < batch_sz && fetch_batch < (compute_batch + stages_count); ++fetch_batch) {
            pipeline.producer_acquire();
            size_t shared_idx = fetch_batch % stages_count;
            size_t batch_idx = fetch_batch;
            // Each thread fetches its own data:
            size_t thread_batch_idx = block_batch(batch_idx) + threadIdx.x;
            // The copy is performed by a single `thread` and the size of the batch is now that of a single element:
            cuda::memcpy_async(thread, shared + shared_offset[shared_idx] + threadIdx.x, global_in + thread_batch_idx, sizeof(int), pipeline);
            pipeline.producer_commit();
        }
        pipeline.consumer_wait();
        block.sync(); // __syncthreads: All memcpy_async of all threads in the block for this stage have completed here
        int shared_idx = compute_batch % stages_count;
        int batch_idx = compute_batch;
        compute(global_out + block_batch(batch_idx), shared + shared_offset[shared_idx]);
        pipeline.consumer_release();
    }
}

如果compute操作只读取与当前线程相同的线程束中的其他线程写入的共享内存,则__syncwarp()就足够了。

7.28.3. Pipeline Interface

cuda::memcpy_async的完整API文档在 libcudacxx API文档沿着一些示例

The pipeline interface requires
pipeline接口需要

  • at least CUDA 11.0,至少为CUDA 11.0,

  • at least ISO C++ 2011 compatibility, e.g., to be compiled with -std=c++11, and
    至少ISO C++ 2011兼容性,例如,使用-std=c++11编译,以及

  • #include <cuda/pipeline>.
    cudaStreamEndCapture().

cuda::memcpy_async的完整API文档在 libcudacxx API文档沿着一些示例。

The pipeline interface requires对于类似C的接口,在不兼容ISO C++ 2011的情况下编译时,请参见管道基元接口

7.28.4. Pipeline Primitives Interface

管道原语是一个类似C的接口,用于memcpy_async功能。通过包含<cuda_pipeline.h>头,可以使用流水线原语接口。在不兼容ISO C++ 2011的情况下编译时,请包含<cuda_pipeline_primitives.h>头文件。

7.28.4.1. memcpy_async Primitive

void __pipeline_memcpy_async(void* __restrict__ dst_shared,
                             const void* __restrict__ src_global,
                             size_t size_and_align,
                             size_t zfill=0);

请求提交以下操作以进行异步评估:

size_t i = 0;
for (; i < size_and_align - zfill; ++i) ((char*)dst_shared)[i] = ((char*)src_global)[i]; /* copy */
for (; i < size_and_align; ++i) ((char*)dst_shared)[i] = 0; /* zero-fill */
  • Requirements:要求:

    • dst_shared must be a pointer to the shared memory destination for the memcpy_async.
      dst_shared必须是指向memcpy_async的共享内存目标的指针。

    • src_global must be a pointer to the global memory source for the memcpy_async.
      src_global必须是指向memcpy_async的全局内存源的指针。

    • size_and_align must be 4, 8, or 16.
      size_and_align必须为4、8或16。

    • zfill <= size_and_align.
      cudaStreamEndCapture().

    • size_and_align must be the alignment of dst_shared and src_global.
      size_and_align必须与dst_sharedsrc_global对齐。

任何线程在等待memcpy_async操作完成之前修改源内存或观察目标内存都是争用条件。在提交memcpy_async操作和等待其完成之间,以下任何操作都会引入争用条件:

  • dst_shared加载。

  • Storing to dst_shared or src_global.
    存储到dst_sharedsrc_global

  • Applying an atomic update to dst_shared or src_global.
    将原子更新应用于dst_sharedsrc_global

7.28.4.2. Commit Primitive

void __pipeline_commit();

将提交的memcpy_async作为当前批处理提交到管道。

7.28.4.3. Wait Primitive

void __pipeline_wait_prior(size_t N);

假设{0, 1, 2, ..., L}是与给定线程对__pipeline_commit()的调用相关联的索引序列。

等待批次完成,至少达到并包括L-N

7.28.4.4. Arrive On Barrier Primitive

void __pipeline_arrive_on(__mbarrier_t* bar);
  • bar指向共享内存中的障碍。

  • 将屏障到达计数递增1,当在此调用之前排序的所有memcpy_async操作都已完成时,到达计数将递减1,因此对到达计数的净影响为零。用户有责任确保到达计数的增量不超过__mbarrier_maximum_count()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值