什么叫Atomic operation(原子操作)呢?或者说在什么情况下我们需要进行Atomic operation呢?
考虑一种情况,我们会经常出现这样一个问题,例如有大量的线程尝试修改少量内存,或者当我们尝试执行读取-修改-写入操作时,将会产生更多的问题,例如加一操作,当多个线程在同一内存位置上执行此操作时,可能会给出错误的输出。
举个实际例子:
假设一个内存位置的初始值为6,并且线程p和q试图增加该内存位置,那么最终答案应为8。但是在执行时,可能会同时读取p和q线程同时获得该值,则两者都将获得值6。他们将其增加到7,并且两者都将把这个7存储在内存中。因此,我们的最终结果不是8而是7,这是错误的。
再举个例子:
以ATM取现为例,假设你的帐户中有5,000人民币并且有两个具有相同帐户的ATM卡。你和朋友同时前往两个不同的ATM机提取4,000人民币。你们两个同时刷卡;因此,当ATM检查余额,则两者都会显示5,000元。当你们俩都取走4,000元,那么两台机器都将查看初始余额,即5,000元。提款金额少于余额,因此两台机器都会给4,000元。即使你的余额为5,000元,也可以获得8,000元,这是危险的。
那么我们就来看下代码吧,首先我们先不使用 automic operation的核函数:
__global__ void histogram_without_atomic(int *d_b, int *d_a){
int tid = threadIdx.x + blockDim.x*blockIdx.x;
int item = d_a[tid];
if(tid<SIZE){
d_b[item]++;
}
}
初始化一个数组,长度为10000,有0-255这256个数,按照计算应该是有部分数字(0-15)有40个,有部分数字(16-255)有39个,但实际上却如下所示:
所以说我们需要使用automic operation,等一个线程成功写入到内存后,另一个线程再进行修改,但是这样也会存在一个问题,就是速度变慢,因为线程之间存在等待。
使用automic operation的核函数
__global__ void histogram_atomic(int *d_b, int *d_a){
int tid = threadIdx.x + blockDim.x * blockIdx.x;
int item = d_a[tid];
if(tid < SIZE){
atomicAdd(&(d_b[item]), 1);
}
}
使用shared memory的核函数:
__global__ void histogram_shared_memory(int *d_b, int *d_a){
int tid = threadIdx.x + blockDim.x*blockIdx.x;
int offset = blockDim.x*gridDim.x;
__shared__ int cache[NUM_BIN];
cache[threadIdx.x] = 0;
__syncthreads();
while(tid < SIZE){
atomicAdd(&(cache[d_a[tid]]), 1);
tid += offset;
}
__syncthreads();
atomicAdd(&(d_b[threadIdx.x]), cache[threadIdx.x]);
}
按理说使用shared memory速度会更加快,但是这个数据量并不是很明显
下面贴出完整代码
#include
#include
#include
#include
using namespace std;
#define SIZE 10000
//#define NUM_BIN 16
#define SECOND_TO_MRCROSECOND (10000000)
#define NUM_BIN 256
__global__ void histogram_without_atomic(int *d_b, int *d_a){
int tid = threadIdx.x + blockDim.x*blockIdx.x;
int item = d_a[tid];
if(tid<SIZE){
d_b[item]++;
}
}
__global__ void histogram_atomic(int *d_b, int *d_a){
int tid = threadIdx.x + blockDim.x * blockIdx.x;
int item = d_a[tid];
if(tid < SIZE){
atomicAdd(&(d_b[item]), 1);
}
}
__global__ void histogram_shared_memory(int *d_b, int *d_a){
int tid = threadIdx.x + blockDim.x*blockIdx.x;
int offset = blockDim.x*gridDim.x;
__shared__ int cache[NUM_BIN];
cache[threadIdx.x] = 0;
__syncthreads();
while(tid < SIZE){
atomicAdd(&(cache[d_a[tid]]), 1);
tid += offset;
}
__syncthreads();
atomicAdd(&(d_b[threadIdx.x]), cache[threadIdx.x]);
}
int main(){
int h_a[SIZE];
for(int i=0; i<SIZE; ++i){
h_a[i] = i%NUM_BIN;
}
int h_b[NUM_BIN];
for(int i = 0; i<NUM_BIN; ++i){
h_b[i] = 0;
}
// Compute histogram by cpu
struct timeval t_start, t_end;
long cost_time = 0;
int hist_cpu[NUM_BIN];
for(int i = 0; i<NUM_BIN; ++i){
hist_cpu[i] = 0;
}
//开始计时
gettimeofday(&t_start, NULL);
for(int i = 0; i < SIZE; ++i){
hist_cpu[h_a[i]] +=1;
}
gettimeofday(&t_end, NULL);
cost_time = t_end.tv_usec-t_start.tv_usec + SECOND_TO_MRCROSECOND*(t_end.tv_sec-t_start.tv_sec); // 以微秒来计数
std::cout << "the programme Cost time: " << cost_time/float(1.0) << " us" << std::endl;
// declare GPU memory pointers
int *d_a;
int *d_b;
cudaEvent_t e_start, e_stop;
cudaEventCreate(&e_start);
cudaEventCreate(&e_stop);
cudaEventRecord(e_start, 0);
// allocate GPU memory
cudaMalloc((void **)&d_a, SIZE*sizeof(int));
cudaMalloc((void **)&d_b, NUM_BIN *sizeof(int));
// transfer the arrays to the GPU
cudaMemcpy(d_a, h_a, SIZE*sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_b, h_b, NUM_BIN*sizeof(int), cudaMemcpyHostToDevice);
//launch the kernel
//histogram_without_atomic<<<((SIZE+NUM_BIN-1)/NUM_BIN), NUM_BIN>>>(d_b, d_a);
histogram_atomic <<<((SIZE+NUM_BIN-1)/NUM_BIN), NUM_BIN>>>(d_b, d_a);
// histogram_shared_memory <<<((SIZE+NUM_BIN-1)/NUM_BIN), NUM_BIN>>>(d_b, d_a);
// copy back the sum from GPU
cudaMemcpy(h_b, d_b, NUM_BIN*sizeof(int), cudaMemcpyDeviceToHost);
cudaEventRecord(e_stop, 0);
cudaEventSynchronize(e_stop);
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, e_start, e_stop);
printf("Time to compute histgram of %d numbers: %3.1f ms\n", SIZE, elapsedTime);
printf("Histogram using 16 bins without shared Memory is: \n");
for(int i = 0; i < NUM_BIN; i++){
printf("bin %d : count %d\n", i, h_b[i]);
}
// free GPU memory allocation
cudaFree(d_a);
cudaFree(d_b);
return 0;
}
参考书籍:
PS:
24426,这不仅仅是个数字,是一个个活生生的人,希望他们都能早日康复,只有失去才知道珍惜,现在希望疫情早点结束,能够不戴口罩,在马路上肆无忌惮的走着就行!
武汉加油!湖北加油!中国加油!↖(ω)↗