CUDA中的原子操作是否保证按每个warp计划?
假设我有8块32个线程,每个线程在GTX970上运行。每个blcok将所有1或所有0写入全局内存中长度为32的数组,其中块中的线程0写入数组中的位置0 现在使用atomicExch写入实际值,将数组中的当前值与块尝试写入的值交换。由于SIMD、原子操作以及扭曲在lockstep中执行的事实,我希望数组在任何时间点只包含1或0。但决不能两者兼而有之 但是,在运行类似这样的代码时,有几种情况下数组在某个时间点包含0和1的混合。这似乎表明了这样一个事实:原子操作不是按照每个扭曲执行的,而是使用其他方案进行调度的 从其他来源来看,我还没有真正找到一篇总结性的文章,详细描述了跨不同经线的原子操作调度(如果我错了,请纠正我),因此我想知道是否有关于这个主题的任何信息。因为我需要将许多由几个32位整数组成的小向量以原子方式写入全局内存,而保证以原子方式写入单个向量的原子操作显然非常重要CUDA中的原子操作是否保证按每个warp计划?,cuda,gpu-atomics,Cuda,Gpu Atomics,假设我有8块32个线程,每个线程在GTX970上运行。每个blcok将所有1或所有0写入全局内存中长度为32的数组,其中块中的线程0写入数组中的位置0 现在使用atomicExch写入实际值,将数组中的当前值与块尝试写入的值交换。由于SIMD、原子操作以及扭曲在lockstep中执行的事实,我希望数组在任何时间点只包含1或0。但决不能两者兼而有之 但是,在运行类似这样的代码时,有几种情况下数组在某个时间点包含0和1的混合。这似乎表明了这样一个事实:原子操作不是按照每个扭曲执行的,而是使用其他方案
对于那些好奇的人来说,我写的代码是在GTX 970上执行的,在compute capability 5.2上编译,使用CUDA 8.0。原子指令和所有指令一样,都是按每个warp调度的。但是,有一个未指定的管道与原子相关联,并且通过管道的预定指令流不能保证在锁步中对管道中的每个线程和每个阶段执行。这就产生了你观察的可能性 我相信一个简单的思维实验会证明这一点:如果同一个扭曲中的两条线指向同一个位置会怎么样?显然,处理的每个方面都无法同步进行。我们可以将这个思维实验扩展到一个SM内,甚至整个SMs中,每个时钟都有多个问题的情况,作为附加示例 如果向量长度足够短(16字节或更少),那么应该可以通过让扭曲中的线程写入适当的向量类型数量(例如
int4
)来完成此(“原子更新”)。只要所有线程(不管它们在网格中的什么位置)都在尝试更新自然对齐的位置,写操作就不应该被其他写操作破坏
然而,在评论中讨论之后,OP的目标似乎是能够让扭曲或螺纹块更新某个长度的向量,而不受其他扭曲或螺纹块的干扰。在我看来,真正需要的是访问控制(这样一次只有一个warp或threadblock在更新一个特定的向量),而OP有一些代码没有按预期工作
可以使用普通的原子操作(atomicCAS
,在下面的示例中)强制执行此访问控制,以便一次只允许一个“生产者”更新向量
下面是一个示例生产者-消费者代码,其中有多个线程块正在更新一系列向量。每个向量“slot”都有一个“slot control”变量,该变量会自动更新以指示:
#include <assert.h>
#include <iostream>
#include <stdio.h>
const int num_slots = 256;
const int slot_length = 32;
const int max_act = 65536;
const int slot_full = 2;
const int slot_filling = 1;
const int slot_empty = 0;
const int max_sm = 64; // needs to be greater than the maximum number of SMs for any GPU that it will be run on
__device__ int slot_control[num_slots] = {0};
__device__ int slots[num_slots*slot_length];
__device__ int observations[max_sm] = {0}; // reported by consumer
__device__ int actives[max_sm] = {0}; // reported by producers
__device__ int correct = 0;
__device__ int block_id = 0;
__device__ volatile int restricted_sm = -1;
__device__ int num_act = 0;
static __device__ __inline__ int __mysmid(){
int smid;
asm volatile("mov.u32 %0, %%smid;" : "=r"(smid));
return smid;}
// this code won't work on a GPU with a single SM!
__global__ void kernel(){
__shared__ volatile int done, update, next_slot;
int my_block_id = atomicAdd(&block_id, 1);
int my_sm = __mysmid();
if (my_block_id == 0){
if (!threadIdx.x){
restricted_sm = my_sm;
__threadfence();
// I am "block 0" and process the vectors, checking for coherency
// "consumer"
next_slot = 0;
volatile int *vslot_control = slot_control;
volatile int *vslots = slots;
int scount = 0;
while(scount < max_act){
if (vslot_control[next_slot] == slot_full){
scount++;
int slot_val = vslots[next_slot*slot_length];
for (int i = 1; i < slot_length; i++) if (slot_val != vslots[next_slot*slot_length+i]) { assert(0); /* badness - incoherence */}
observations[slot_val]++;
vslot_control[next_slot] = slot_empty;
correct++;
__threadfence();
}
next_slot++;
if (next_slot >= num_slots) next_slot = 0;
}
}}
else {
// "producer"
while (restricted_sm < 0); // wait for signaling
if (my_sm == restricted_sm) return;
next_slot = 0;
done = 0;
__syncthreads();
while (!done) {
if (!threadIdx.x){
while (atomicCAS(slot_control+next_slot, slot_empty, slot_filling) > slot_empty) {
next_slot++;
if (next_slot >= num_slots) next_slot = 0;}
// we grabbed an empty slot, fill it with my_sm
if (atomicAdd(&num_act, 1) < max_act) update = 1;
else {done = 1; update = 0;}
}
__syncthreads();
if (update) slots[next_slot*slot_length+threadIdx.x] = my_sm;
__threadfence(); //enforce ordering
if ((update) && (!threadIdx.x)){
slot_control[next_slot] = 2; // mark slot full
atomicAdd(actives+my_sm, 1);}
__syncthreads();
}
}
}
int main(){
kernel<<<256, slot_length>>>();
cudaDeviceSynchronize();
cudaError_t res= cudaGetLastError();
if (res != cudaSuccess) printf("kernel failure: %d\n", (int)res);
int *h_obs = new int[max_sm];
int *h_act = new int[max_sm];
int h_correct;
cudaMemcpyFromSymbol(h_obs, observations, sizeof(int)*max_sm);
cudaMemcpyFromSymbol(h_act, actives, sizeof(int)*max_sm);
cudaMemcpyFromSymbol(&h_correct, correct, sizeof(int));
int h_total_act = 0;
int h_total_obs = 0;
for (int i = 0; i < max_sm; i++){
std::cout << h_act[i] << "," << h_obs[i] << " ";
h_total_act += h_act[i];
h_total_obs += h_obs[i];}
std::cout << std::endl << h_total_act << "," << h_total_obs << "," << h_correct << std::endl;
}
#包括
#包括
#包括
const int num_slots=256;
const int slot_length=32;
const int max_act=65536;
const int slot_full=2;
const int slot_filling=1;
const int slot_empty=0;
常量int max_sm=64;//需要大于将在其上运行的任何GPU的最大SMs数
__设备\uuuu int slot\u控制[num\u slots]={0};
__设备\uuuuu int插槽[插槽数量*插槽长度];
__设备内部观测值[max\u sm]={0};//消费者报告
__设备\uuuu int活动[max\u sm]={0};//生产商报告
__设备_uu_uuint correct=0;
__设备\uuuuu int块\u id=0;
__设备uuuuuuvolatile int restricted_sm=-1;
__设备数量=0;
静态\uuuuuuuuuuuuuuuuuuuuu设备\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu{
int-smid;
asm volatile(“mov.u32%0,%%smid;”:“=r”(smid));
返回smid;}
//这段代码在带有单个SM的GPU上不起作用!
__全局无效内核(){
__共享\ volatile int完成,更新,下一个\槽;
int my_block_id=atomicAdd(&block_id,1);
int my_sm=\uu mysmid();
if(my_block_id==0){
如果(!threadIdx.x){
受限的\u sm=我的\u sm;
__螺纹围栏();
//我是“块0”,处理向量,检查一致性
//“消费者”
下一个_槽=0;
volatile int*vslot\u control=slot\u control;
volatile int*vslots=插槽;
int-scont=0;
同时(童子军<最大行动){
if(vslot_控制[下一个_插槽]==插槽_已满){
Scont++;
int slot_val=vslots[next_slot*slot_length];
对于(int i=1;i=num\u插槽)下一个\u插槽=0;
}
}}
否则{
//“制作人”
while(restricted_sm<0);//等待信令
如果(my_sm==受限_sm)返回;
下一个_槽=0;
完成=0;
__同步线程();
而(!完成){
如果(!threadIdx.x){
while(atomicCAS(插槽控制+下一个插槽,插槽清空,插槽填充)>插槽清空){
下一个插槽++;
如果(下一个\u插槽>=num\u插槽)下一个\u插槽=0;}
//我们抢了一个空的