函数执行空间说明符表示函数是在主机上执行还是在设备上执行,以及它是可从主机调用还是可从设备调用。
__global__
执行空间说明符将函数声明为内核。这样的函数是:Executed on the device,在设备上执行,
Callable from the host,可从主机调用,
对于计算能力为5.0或更高版本的设备,可从设备调用(有关详细信息,请参阅CUDA动态并行性)。
__global__
函数必须具有void返回类型,并且不能是类的成员。
对__global__
函数的任何调用都必须指定其执行配置,如中所述 执行配置.
对__global__
函数的调用是异步的,这意味着它在设备完成执行之前返回。
__device__执行空间说明符声明一个函数,该函数:
Executed on the device,在设备上执行,
Callable from the device only.
__global__
和__device__
执行空间说明符不能一起使用。
__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
}
在以下情况下,“交叉执行空间”调用具有未定义的行为:
定义了__CUDA_ARCH__,从__global__
, __device__
or __host__ __device__
函数内部调用__host__。
__CUDA_ARCH__未定义,从__host__函数内部调用__device__函数。9
编译器在认为合适时内联任何__device__
函数。
__noinline__
函数限定符可以用作编译器的提示,如果可能的话,不要内联函数。
__forceinline__
函数限定符可以用来强制编译器内联函数。
__noinline__
和__forceinline__
函数限定符不能一起使用,两个函数限定符都不能应用于内联函数。
在设备代码中声明的、没有本节所述的__device__
、__shared__
和__constant__
存储器空间说明符中的任何一个的自动变量通常驻留在寄存器中。但是,在某些情况下,编译器可能会选择将其放置在本地内存中,这可能会对性能产生不利影响,详见设备内存访问。
__device__
内存空间说明符声明驻留在设备上的变量。
在接下来的三个部分中定义的其他内存空间说明符中的至多一个可以与__device__
一起使用,以进一步表示变量属于哪个内存空间。如果它们都不存在,则变量:
驻留在全局内存空间中,
具有创建它的CUDA上下文的生命周期,
每个设备具有不同的对象,
可从网格内的所有线程以及主机通过运行时库(cudaGetSymbolAddress()
/ cudaGetSymbolSize()
/ cudaMemcpyToSymbol()
/ cudaMemcpyFromSymbol()
访问)。
__constant__内存空间说明符(可选择与__device__一起使用)声明一个变量,该变量:
Resides in constant memory space,驻留在恒定的存储空间中,
__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.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.
__managed__内存空间说明符(可选择与__device__一起使用)声明一个变量,该变量:
__constant__内存空间说明符(可选择与__device__一起使用)声明一个变量,该变量:
可以从设备和主机代码引用,例如,可以获取其地址,也可以直接从设备或主机函数读取或写入。
Has the lifetime of an application.具有应用程序的生存期。
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类型语言中,指针a
、b
和c
可以被别名化,因此通过c
的任何写入可以修改a
或b
的元素。这意味着为了保证功能正确性,编译器不能将a[0]
和b[0]
加载到寄存器中,将它们相乘,并将结果存储到c[0]
和c[1]
,因为如果a[0]
与c[0]
实际上是相同的位置,则结果将与抽象执行模型不同。因此编译器不能利用公共子表达式。同样地,编译器不能仅仅将c[4]
的计算重新排序到c[0]
和c[1]
的计算附近,因为之前对c[3]
的写入可能改变对c[4]
的计算的输入。
通过使a
、b
和c
受限指针,程序员向编译器断言这些指针实际上没有别名,在这种情况下,这意味着通过c
的写入永远不会覆盖a
或b
的元素。这将更改函数原型,如下所示:
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代码的性能产生负面影响。
这些是从基本整数和浮点类型派生的向量类型。它们是结构,并且 1st, 2nd, 3rd, and 4th件可分别通过字段x
、y
、z
和w
访问。它们都带有一个构造函数,其形式为make_
;例如,
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 |
此类型是基于uint3
的整数向量类型,用于指定维度。当定义dim3
类型的变量时,任何未指定的分量都被初始化为1。
内置变量指定栅格和块尺寸以及块和线程索引。它们仅在设备上执行的功能中有效。
此变量的类型为dim3
(参见dim3),包含网格的尺寸
这个变量的类型是uint3
(参见char,short,int,long,longlong,float,double),它包含网格中的块索引。
此变量的类型为dim3
(参见dim3),包含块的尺寸。
这个变量的类型是uint3
(参见char,short,int,long,longlong,float,double),它包含了块中的线程索引。
此变量的类型为int
,包含线程中的This variable is of type int
and contains the warp size in threads (see SIMT Architecture for the definition of a warp).
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;
}
两个线程同时从相同的存储器位置X
和Y
读取和写入。任何数据争用都是未定义的行为,并且没有定义的语义。A
和B
的结果值可以是任何值。
存储器围栏函数可用于对存储器存取实施顺序一致的排序。内存围栏函数的不同之处在于执行排序的范围,但它们独立于访问的内存空间(共享内存、全局内存、页锁定主机内存和对等设备的内存)。
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;
}
}
}
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()
,且接着安全地读取由线程束中的其它线程存储的值。
参考手册列出了设备代码支持的所有C/C++标准库数学函数,以及仅设备代码支持的所有内在函数。
“数学函数”提供了其中某些函数的相关精度信息。
中介绍了纹理对象 纹理对象API
中介绍了纹理提取 纹理提取.
template
T tex1Dfetch(cudaTextureObject_t texObj, int x);
使用整数纹理坐标texObj
从由一维纹理对象x
指定的线性存储器的区域中提取。tex1Dfetch()
仅适用于非归一化坐标,因此仅支持边界和箝位寻址模式。它不执行任何纹理过滤。对于整数类型,它可以选择将整数提升为单精度浮点。
template
T tex1D(cudaTextureObject_t texObj, float x);
使用纹理坐标texObj
从由一维纹理对象x
指定的CUDA数组中获取。
template
T tex1DLod(cudaTextureObject_t texObj, float x, float level);
从由一维纹理对象texObj
指定的CUDA数组中使用纹理坐标x
在细节层次level
处进行提取。
template
T tex1DGrad(cudaTextureObject_t texObj, float x, float dx, float dy);
使用纹理坐标texObj
从由一维纹理对象x
指定的CUDA数组中获取。从X梯度dx
和Y梯度dy
导出细节层次
template
T tex2D(cudaTextureObject_t texObj, float x, float y);
使用纹理坐标texObj
从CUDA数组或由二维纹理对象(x,y)
指定的线性存储器的区域中提取。
template
T tex2D(cudaTextureObject_t texObj, float x, float y, bool* isResident);
使用纹理坐标texObj
从二维纹理对象(x,y)
指定的CUDA数组中获取。还通过isResident
指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。
template
T tex2Dgather(cudaTextureObject_t texObj,
float x, float y, int comp = 0);
使用纹理坐标texObj
和x
以及如中所述的y
参数从由2D纹理对象comp
指定的CUDA数组中提取 纹理聚集.
template
T tex2Dgather(cudaTextureObject_t texObj,
float x, float y, bool* isResident, int comp = 0);
使用纹理坐标texObj
和x
以及如中所述的y
参数从由2D纹理对象comp
指定的CUDA数组中提取 纹理聚集.还通过isResident
指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。
7.8.1.9. tex2DGrad()
template
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
T tex2DGrad(cudaTextureObject_t texObj, float x, float y,
float2 dx, float2 dy, bool* isResident);
使用纹理坐标texObj
从二维纹理对象(x,y)
指定的CUDA数组中获取。细节层次从dx
和dy
梯度导出。还通过isResident
指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。
7.8.1.11. tex2DLod()
template
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
tex2DLod(cudaTextureObject_t texObj, float x, float y, float level, bool* isResident);
从由二维纹理对象texObj
指定的CUDA数组中使用纹理坐标(x,y)
在细节级别level
处进行提取。还通过isResident
指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。
7.8.1.13. tex3D()
template
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
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
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
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
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
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
T tex1DLayered(cudaTextureObject_t texObj, float x, int layer);
使用纹理坐标texObj
和索引x
从由一维纹理对象layer
指定的CUDA数组中提取,如 分层纹理
7.8.1.20. tex1DLayeredLod()
template
T tex1DLayeredLod(cudaTextureObject_t texObj, float x, int layer, float level);
从一维数组指定的CUDA数组中读取 层状结构在层layer
处使用纹理坐标x
和细节级别level
。
7.8.1.21. tex1DLayeredGrad()
template
T tex1DLayeredGrad(cudaTextureObject_t texObj, float x, int layer,
float dx, float dy);
从一维数组指定的CUDA数组中读取 层状结构在层layer
处使用纹理坐标x
和从dx
和dy
梯度导出的细节水平。
7.8.1.22. tex2DLayered()
template
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
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
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
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
T tex2DLayeredGrad(cudaTextureObject_t texObj, float x, float y, int layer,
float2 dx, float2 dy);
从二维数组指定的CUDA数组中读取 层状结构在层layer
处使用纹理坐标(x,y)
和从dx
和dy
梯度导出的细节水平。
7.8.1.27. tex2DLayeredGrad() for sparse CUDA arrays
template
T tex2DLayeredGrad(cudaTextureObject_t texObj, float x, float y, int layer,
float2 dx, float2 dy, bool* isResident);
从二维数组指定的CUDA数组中读取 层状结构在层layer
处使用纹理坐标(x,y)
和从dx
和dy
梯度导出的细节水平。还通过isResident
指针返回纹理元素是否驻留在内存中。否则,获取的值将为零。
7.8.1.28. texCubemap()
template
T texCubemap(cudaTextureObject_t texObj, float x, float y, float z);
7.8.1.29. texCubemapGrad()
template
T texCubemapGrad(cudaTextureObject_t texObj, float x, float, y, float z,
float4 dx, float4 dy);
使用纹理坐标texObj
从立方体贴图纹理对象(x,y,z)
指定的CUDA数组中获取,如中所述 立方体图纹理.所使用的细节层次源自dx
和dy
梯度。
7.8.1.30. texCubemapLod()texCubemapLod()
template
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
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
T texCubemapLayeredGrad(cudaTextureObject_t texObj, float x, float y, float z,
int layer, float4 dx, float4 dy);
使用纹理坐标texObj
和索引(x,y,z)
从立方体贴图分层纹理对象layer
指定的CUDA数组中提取,如立方体贴图分层纹理中所述,细节级别从dx
和dy
渐变派生。
7.8.1.33. texCubemapLayeredLod()
template
T texCubemapLayeredLod(cudaTextureObject_t texObj, float x, float y, float z,
int layer, float level);
使用纹理坐标texObj
和索引(x,y,z)
从立方体贴图分层纹理对象layer
指定的CUDA数组中提取,如立方体贴图分层纹理中所述,在细节级别level
。
中介绍了曲面对象 曲面对象API
在下面的部分中,boundaryMode
指定边界模式,即如何处理超出范围的表面坐标;它等于cudaBoundaryModeClamp
,在这种情况下,超出范围的坐标被箝位到有效范围,或者等于cudaBoundaryModeZero
,在这种情况下,超出范围的读取返回零并且超出范围的写入被忽略,或者等于cudaBoundaryModeTrap
,在这种情况下,超出范围的访问导致内核执行失败。
7.9.1.1. surf1Dread()
template
T surf1Dread(cudaSurfaceObject_t surfObj, int x,
boundaryMode = cudaBoundaryModeTrap);
使用字节坐标x读取由一维表面对象surfObj
指定的CUDA数组。
7.9.1.2. surf1Dwrite
template
void surf1Dwrite(T data,
cudaSurfaceObject_t surfObj,
int x,
boundaryMode = cudaBoundaryModeTrap);
将值数据写入由字节坐标x处的一维表面对象surfObj
指定的CUDA阵列。
7.9.1.3. surf2Dread()
template
T surf2Dread(cudaSurfaceObject_t surfObj,
int x, int y,
boundaryMode = cudaBoundaryModeTrap);
template
void surf2Dread(T* data,
cudaSurfaceObject_t surfObj,
int x, int y,
boundaryMode = cudaBoundaryModeTrap);
使用字节坐标x和y读取由二维表面对象surfObj
指定的CUDA数组。
7.9.1.4. surf2Dwrite()
template
void surf2Dwrite(T data,
cudaSurfaceObject_t surfObj,
int x, int y,
boundaryMode = cudaBoundaryModeTrap);
将值数据写入由字节坐标x和y处的二维表面对象surfObj
指定的CUDA数组。
7.9.1.5. surf3Dread()
template
T surf3Dread(cudaSurfaceObject_t surfObj,
int x, int y, int z,
boundaryMode = cudaBoundaryModeTrap);
template
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
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
T surf1DLayeredread(
cudaSurfaceObject_t surfObj,
int x, int layer,
boundaryMode = cudaBoundaryModeTrap);
template
void surf1DLayeredread(T data,
cudaSurfaceObject_t surfObj,
int x, int layer,
boundaryMode = cudaBoundaryModeTrap);
使用字节坐标x和索引surfObj
读取由一维分层表面对象layer
指定的CUDA阵列。
7.9.1.8. surf1DLayeredwrite()
template
void surf1DLayeredwrite(T data,
cudaSurfaceObject_t surfObj,
int x, int layer,
boundaryMode = cudaBoundaryModeTrap);
将值数据写入由字节坐标x和索引surfObj
处的二维分层表面对象layer
指定的CUDA阵列。
7.9.1.9. surf2DLayeredread()
template
T surf2DLayeredread(
cudaSurfaceObject_t surfObj,
int x, int y, int layer,
boundaryMode = cudaBoundaryModeTrap);
template
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
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
T surfCubemapread(
cudaSurfaceObject_t surfObj,
int x, int y, int face,
boundaryMode = cudaBoundaryModeTrap);
template
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
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
T surfCubemapLayeredread(
cudaSurfaceObject_t surfObj,
int x, int y, int layerFace,
boundaryMode = cudaBoundaryModeTrap);
template
void surfCubemapLayeredread(T data,
cudaSurfaceObject_t surfObj,
int x, int y, int layerFace,
使用字节坐标x和y以及索引layerFace读取立方体图分层曲面对象surfObj指定的CUDA数组。
7.9.1.14. surfCubemapLayeredwrite()
template
void surfCubemapLayeredwrite(T data,
cudaSurfaceObject_t surfObj,
int x, int y, int layerFace,
boundaryMode = cudaBoundaryModeTrap);
将值数据写入CUDA数组,该数组由字节坐标x和y处的立方体图分层对象surfObj以及索引layerFace指定。
只读数据缓存加载功能仅受计算能力为5.0及更高版本的设备支持。
T __ldg(const T* address);
返回位于地址T
的类型address
的数据,其中T
是char
、signed char
、short
、int
、long
、long long
unsigned char
、unsigned short
、unsigned int
、unsigned long
、unsigned long long
、char2
、char4
、short2
、short4
、int2
、int4
、longlong2
uchar2
、uchar4
、ushort2
,ushort4
、uint2
、uint4
、ulonglong2
float
、float2
、float4
、double
或double2
。在包含cuda_fp16.h
头的情况下,T
可以是__half
或__half2
。类似地,在包括cuda_bf16.h
报头的情况下,T
也可以是__nv_bfloat16
或__nv_bfloat162
。该操作缓存在只读数据缓存中(请参见全局内存)。
只有计算能力为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
的数据,其中T
是char
、signed char
、short
、int
、long
、long long
unsigned char
、unsigned short
、unsigned int
、unsigned long
、unsigned long long
、char2
、char4
、short2
、short4
、int2
、int4
、longlong2
uchar2
、uchar4
、ushort2
,ushort4
、uint2
、uint4
、ulonglong2
float
、float2
、float4
、double
或double2
。在包含cuda_fp16.h
头的情况下,T
可以是__half
或__half2
。类似地,在包括cuda_bf16.h
报头的情况下,T
也可以是__nv_bfloat16
或__nv_bfloat162
。该操作正在使用相应的缓存操作符(参见PTX伊萨)
只有计算能力为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);
将类型value
的T
变元存储到地址address
处的位置,其中T
是char
、signed char
、short
、int
、long
、long long
unsigned char
、unsigned short
、unsigned int
、unsigned long
、unsigned long long
、char2
、char4
、short2
、short4
、int2
、int4
、longlong2
uchar2
、uchar4
,ushort2
、ushort4
、uint2
、uint4
、ulonglong2
float
、float2
、float4
、double
或double2
。在包含cuda_fp16.h
头的情况下,T
可以是__half
或__half2
。类似地,在包括cuda_bf16.h
报头的情况下,T
也可以是__nv_bfloat16
或__nv_bfloat162
。该操作正在使用相应的缓存操作符(参见PTX伊萨)
clock_t clock();
long long int clock64();
当在设备代码中执行时,返回每个时钟周期递增的每个多处理器计数器的值。在内核的开始和结束时对该计数器进行采样,取两个采样的差,并且记录每个线程的结果,为每个线程提供了设备完全执行该线程所花费的时钟周期数的度量,而不是设备实际花费在执行线程指令上的时钟周期数的度量。前者的数目大于后者,因为线程是时间分片的。
原子功能对驻留在全局或共享存储器中的一个32位或64位字执行读-修改-写原子操作。在float2
或float4
的情况下,对驻留在全局存储器中的向量的每个元素执行读取-修改-写入操作。例如,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.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位访问是原子的。
float2
的float4
和atomicAdd()
浮点向量版本仅受计算能力为9.x及更高版本的设备支持。对于两个或四个float2
元素中的每一个,分别保证float4
或float
加法操作的原子性;不能保证整个float2
或float4
作为单个64位或128位访问是原子的。
__half
的16位atomicAdd()
浮点版本仅受计算能力为7.x及更高版本的设备支持。
__nv_bfloat16
的16位atomicAdd()
浮点版本仅受计算能力为8.x及更高版本的设备支持。
float2
的float4
和atomicAdd()
浮点向量版本仅受计算能力为9.x及更高版本的设备支持。
float2
的float4
和atomicAdd()
浮点向量版本仅支持全局内存地址。
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
,计算old
和val
的最小值,并将结果存储回相同地址处的存储器。这三个操作在一个原子事务中执行。函数返回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
,计算old
和val
的最大值,并将结果存储回存储器的相同地址。这三个操作在一个原子事务中执行。函数返回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.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
。
__device__ unsigned int __isGlobal(const void *ptr);
如果ptr
包含全局内存空间中对象的通用地址,则返回1,否则返回0。
__device__ unsigned int __isShared(const void *ptr);
如果ptr
包含共享内存空间中对象的通用地址,则返回1,否则返回0。
__device__ unsigned int __isConstant(const void *ptr);
如果ptr
包含常量内存空间中对象的泛型地址,则返回1,否则返回0
__device__ unsigned int __isGridConstant(const void *ptr);
如果ptr
包含用__grid_constant__
注释的内核参数的泛型地址,则返回1,否则返回0。仅支持高于或等于7.x或更高版本的计算体系结构。
__device__ unsigned int __isLocal(const void *ptr);
如果ptr
包含对象在本地内存空间中的通用地址,则返回1,否则返回0。
__device__ size_t __cvta_generic_to_global(const void *ptr);
返回在由cvta.to.local
表示的通用地址上执行PTXptr
指令的结果。
__device__ size_t __cvta_generic_to_shared(const void *ptr);
返回在由cvta.to.local
表示的通用地址上执行PTXptr
指令的结果。
__device__ size_t __cvta_generic_to_constant(const void *ptr);
返回在由cvta.to.local
表示的通用地址上执行PTXptr
指令的结果。
__device__ size_t __cvta_generic_to_local(const void *ptr);
返回在由cvta.to.local
表示的通用地址上执行PTXptr
指令的结果。
__device__ void * __cvta_global_to_generic(size_t rawbits);
返回通过对cvta.global
提供的值执行PTXrawbits
指令获得的通用指针
__device__ void * __cvta_shared_to_generic(size_t rawbits);
返回通过对cvta.shared
提供的值执行PTXrawbits
指令获得的通用指针。
__device__ void * __cvta_constant_to_generic(size_t rawbits);
返回通过对cvta.const
提供的值执行PTXrawbits
指令获得的通用指针。
__device__ void * __cvta_local_to_generic(size_t rawbits);
返回通过对cvta.local
提供的值执行PTXrawbits
指令获得的通用指针。
__host__ __device__ void * alloca(size_t size);
alloca()
函数在调用者的堆栈帧中分配size
字节的内存。返回值是指向已分配内存的指针,当从设备代码调用函数时,内存的开头是16字节对齐的。当alloca()
的调用者返回时,分配的内存会自动释放。
__device__ void foo(unsigned int num) {
int4 *ptr = (int4 *)alloca(num * sizeof(int4));
// use of ptr
...
}
本节介绍的函数可用于向编译器优化器提供附加信息。
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,
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.
void __builtin_assume(bool exp)
允许编译器假定布尔参数为true。如果该参数在运行时不为真,则该行为未定义。注意,如果参数有副作用,则行为是未指定的。
__device__ int get(int *ptr, int idx) {
__builtin_assume(idx <= 2);
return ptr[idx];
}
void __assume(bool exp)
允许编译器假定布尔参数为true。如果该参数在运行时不为真,则该行为未定义。注意,如果参数有副作用,则行为是未指定的。
__device__ int get(int *ptr, int idx) {
__assume(idx <= 2);
return ptr[idx];
}
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 ();
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();
}
仅在使用__assume()
主机编译器时支持cl.exe
。其他功能在所有平台上均受支持,但有以下限制:
如果宿主编译器支持该函数,则可以从翻译单元中的任何位置调用该函数。
否则,该函数必须从__device__
函数体中调用,或者仅在定义了__global__
宏时调用12。
int __all_sync(unsigned mask, int predicate);
int __any_sync(unsigned mask, int predicate);
unsigned __ballot_sync(unsigned mask, int predicate);
unsigned __activemask();
__match_any_sync
和__match_all_sync
执行变量的广播和比较操作
unsigned int __match_any_sync(unsigned mask, T value);
unsigned int __match_all_sync(unsigned mask, T value, int *pred);
T
可以是int
、unsigned int
、long
、unsigned long
、long long
、unsigned long long
、float
或double
。
__match_sync()
intrinsic允许在同步value
中命名的线程之后,跨线程束中的线程广播和比较值mask
。
__match_any_sync
返回在value
中具有相同值mask
的线程的掩码
__match_all_sync
如果mask
中所有线程的mask
值相同,则返回value
;否则返回0。如果pred
中的所有线程具有相同的值mask
,则谓词value
被设置为真;否则将谓词设置为假。
新的*_sync
match intrinsic接受一个掩码,指示参与调用的线程。必须为每个参与线程设置一个表示线程通道ID的位,以确保它们在硬件执行内在函数之前正确收敛。每个调用线程必须在掩码中设置自己的位,并且所有在掩码中命名的未退出线程必须使用相同的掩码执行相同的内在函数,否则结果是未定义的。
这些内部函数并不意味着内存障碍。它们不保证任何内存顺序。
CUDA 11通过memcpy_async
API引入了异步数据操作,允许设备代码显式管理数据的异步复制。memcpy_async
特性使CUDA内核能够将计算与数据移动重叠。
7.27.1. memcpy_async
API
memcpy_async
API在cuda/barrier
、cuda/pipeline
和cooperative_groups/memcpy_async.h
头文件中提供。
cuda::memcpy_async
API使用cuda::barrier
和cuda::pipeline
同步原语,而cooperative_groups::memcpy_async
使用coopertive_groups::wait
同步。
这些API具有非常相似的语义:将对象从src
复制到dst
,就像由另一个线程执行一样,在复制完成时,可以通过cuda::pipeline
、cuda::barrier
或cooperative_groups::wait
同步。
提供了用于cuda::memcpy_async
和cuda::barrier
的cuda::pipeline
重载的完整API文档 libcudacxx API文档沿着一些示例。
cooperative_groups::memcpy_async在文档的cooperative_groups::memcpy_async cooperative groupscooperative_groups::memcpy_async协作组部分中提供。
使用 cuda::barrier cuda::barriercuda::barrier和memcpy_async
的cuda::pipeline
API需要7.0或更高的计算能力。在具有8.0或更高计算能力的设备上,从全局到共享内存的memcpy_async
操作可以受益于硬件加速。
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
功能的情况下表达此模式:
不 使用memcpy_async一节介绍了一个示例,该示例不将计算与数据移动重叠,而是使用中间寄存器来复制数据。
h memcpy_async一节通过引入cooperative_groups::memcpy_async和cuda::memcpy_async
API来直接将数据从全局复制到共享内存,而无需使用中间寄存器,从而改进了前面的示例。
使用 cuda::barrier进行异步数据复制一节显示了具有协作组和barrier的memcpy
部分 使用cuda::pipeline使用单阶段管道进行单阶段异步数据复制show memcpy
使用 cuda::pipeline显示具有多阶段管道的memcpy一节多阶段异步数据复制
memcpy_async
在没有memcpy_async
的情况下,复制和计算模式的复制阶段被表示为shared[local_idx] = global[global_idx]
。这种全局到共享内存的复制扩展为从全局内存读取寄存器,然后从寄存器写入共享内存。
当这种模式出现在迭代算法中时,每个线程块都需要在shared[local_idx]=global[global_idx]
分配之后同步,以确保在计算阶段开始之前完成对共享内存的所有写入。线程块还需要在计算阶段之后再次同步,以防止在所有线程完成计算之前覆盖共享内存。下面的代码片段演示了此模式。
#include
__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
}
}
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
#include
__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();
}
}}
cuda::barrier
cuda::barrier的cuda::memcpy_async重载允许使用barrier
同步异步数据传输。此重载通过以下方式执行复制操作,就像由绑定到屏障的另一个线程执行一样:在创建时递增当前阶段的预期计数,并且在复制操作完成时递减它,使得barrier
的阶段将仅在参与屏障的所有线程已经到达并且绑定到屏障的当前阶段的所有memcpy_async
已经完成时前进。以下示例使用块范围的barrier
,其中所有块线程都参与,并将等待操作与屏障arrive_and_wait
交换,同时提供与前一示例相同的功能:
#include
#include
__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 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();
}
}
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_tmemcpy_async
的两个指针都与Align
对齐边界对齐,并且size
是Align
的倍数,通过将其作为参数传递,其中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
或pipeline::consumer_wait()
以等待感知序列TB
中的批处理完成。注意pipeline::consumer_wait()
等同于pipeline_consumer_wait_prior
,其中N=PL
。
pipeline_consumer_wait_prior
函数等待实际序列中的批次,至少直到并包括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
重新收敛。
cuda::pipeline
CUDA提供了cuda::pipeline
同步对象来管理异步数据移动并将其与计算重叠。
cuda::pipeline
的API文档在 libcudacxx API。流水线对象是具有头部和尾部的双端N级队列,并且用于以先进先出(FIFO)顺序处理工作。管道对象具有以下成员函数来管理管道的阶段。
Pipeline Class Member Function管道类成员函数 |
Description说明 |
---|---|
|
Acquires an available stage in the pipeline internal queue. |
|
Commits the asynchronous operations issued after the producer_acquire call on the currently acquired stage of the pipeline. |
|
Wait for completion of all asynchronous operations on the oldest stage of the pipeline. |
|
Release the oldest stage of the pipeline to the pipeline object for reuse. The released stage can be then acquired by the producer. |
cuda::pipeline
在前面的例子中,我们展示了如何使用cooperative_groups和cuda::barrier进行异步数据传输。在本节中,我们将使用带有单个阶段的cuda::pipeline
API来调度异步拷贝。稍后,我们将扩展此示例以显示多阶段重叠计算和拷贝。
#include
#include
__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();
}
}
cuda::pipeline
在前面使用 cooperative_groups::wait和cuda::barrier的示例中,内核线程立即等待到共享内存的数据传输完成。这避免了数据从全局存储器传输到寄存器,但不会通过重叠计算隐藏memcpy_async
操作的延迟。
为此,我们使用CUDA 管线下例中的功能。它提供了一种管理memcpy_async
批处理序列的机制,使CUDA内核能够将内存传输与计算重叠。下面的示例实现了一个两阶段的管道,该管道将数据传输与计算重叠。它:
初始化管道共享状态(详细信息如下)
通过为第一个批次安排memcpy_async
来启动管道。
在所有批次上循环:它为下一批调度memcpy_async
,在完成前一批的memcpy_async
时阻塞所有线程,然后将前一批的计算与下一批的存储器的异步副本重叠。
最后,它通过在最后一个批处理上执行计算来排空管道。
注意,为了与cuda::pipeline
的互操作性,这里使用来自cuda::memcpy_async
报头的cuda/pipeline
。
#include
#include
__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
__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
原语非常灵活,并且支持我们上面的示例没有使用的两个特性:块中线程的任意子集可以参与pipeline
,并且从参与的线程中,任何子集可以是生产者、消费者或两者。在下面的示例中,线程等级为“even”的线程是生产者,而其他线程是消费者:
__device__ void compute(int* global_out, int shared_in);
template
__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
与pipeline
的组合来做得比__syncthreads()
更好:
template
__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 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()
就足够了。
cuda::memcpy_async
的完整API文档在 libcudacxx API文档沿着一些示例
The pipeline
interface requirespipeline
接口需要
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
.cudaStreamEndCapture()
.
cuda::memcpy_async
的完整API文档在 libcudacxx API文档沿着一些示例。
The pipeline
interface requires对于类似C的接口,在不兼容ISO C++ 2011的情况下编译时,请参见管道基元接口。
管道原语是一个类似C的接口,用于memcpy_async
功能。通过包含
头,可以使用流水线原语接口。在不兼容ISO C++ 2011的情况下编译时,请包含
头文件。
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_shared
和src_global
对齐。
任何线程在等待memcpy_async
操作完成之前修改源内存或观察目标内存都是争用条件。在提交memcpy_async
操作和等待其完成之间,以下任何操作都会引入争用条件:
从dst_shared
加载。
Storing to dst_shared
or src_global
.
存储到dst_shared
或src_global
。
Applying an atomic update to dst_shared
or src_global
.
将原子更新应用于dst_shared
或src_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()
。