以下两个功能有什么区别?
__device__ inline void comparator_volatile(volatile float &A, volatile float &B, uint dir) {
float t;
if ((A > B) == dir) {
t = A;
A = B;
B = t;
}
}
__device__ inline void comparator(float &A, float &B, uint dir) {
float t;
if ((A > B) == dir) {
t = A;
A = B;
B = t;
}
__threadfence();
}
有人可以帮助我吗?
我在基于CUDA SDK版本的一些不同版本中实现了BitonicSort。 对于ATOMIC版本(bitonicSortAtomic),我尝试在__syncblocks_atomic中使用__threadfence()来保持内存一致性。但它不起作用(输出不正确)。我必须调用comparator_volatile而不是比较器,然后我得到正确的结果。任何的想法? BitonicSort基准:
// (C) Copyright 2013, University of Illinois. All Rights Reserved
#include <stdlib.h>
#include <stdio.h>
#include "parboil.h"
#define THREADS 256
#define BLOCKS 32
#define NUM_VALS 2*THREADS*BLOCKS
__device__ volatile int mutex = 0;
__device__ inline void __syncblocks_atomic(int goal) {
__syncthreads();
// __threadfence();
int tx = threadIdx.x;
if (tx == 0) {
atomicAdd((int *)&mutex, 1);
while(g_mutex != goal) {}
}
__syncthreads();
}
__device__ inline void comparator(float &A, float &B, uint dir) {
float t;
if ((A > B) == dir) {
t = A;
A = B;
B = t;
}
}
__device__ inline void comparator_volatile(volatile float &A, volatile float &B, uint dir) {
float t;
if ((A > B) == dir) {
t = A;
A = B;
B = t;
}
}
#ifdef NAIVE
__global__ void bitonicSortNaive(float *src, int stride, int size) {
unsigned int tid = threadIdx.x + blockDim.x * blockIdx.x;
uint dir = (tid & (size / 2)) == 0;
unsigned int pos = 2*tid - (tid & (stride - 1));
comparator(src[pos], src[pos+stride], dir);
}
#endif
#ifdef ATOMIC
__global__ void bitonicSortAtomic(float *src, int length) {
uint numBlocks = gridDim.x * gridDim.y * gridDim.z;
uint goalVal = 0;
uint tid = threadIdx.x + blockDim.x * blockIdx.x;
for(uint size=2; size<=length; size<<=1) {
for(uint stride=size>>1; stride>0; stride=stride>>1) {
uint dir = (tid & (size / 2)) == 0;
uint pos = 2*tid - (tid & (stride - 1));
comparator_volatile(src[pos], src[pos+stride], dir);
if(stride>THREADS || (stride==1 && size>=THREADS)) {
goalVal += numBlocks;
__syncblocks_atomic(goalVal);
}
else
__syncthreads();
} // end for stride
} // end for size
}
#endif
int main() {
printf("[BENCH] Bitonic Sort %d elements\n", NUM_VALS);
printf("[BENCH] Xuhao Chen <cxh@illinois.edu>\n");
#ifdef NAIVE
printf("[BENCH] Naive version\n");
#endif
#ifdef ATOMIC
printf("[BENCH] Atomic Barrier\n");
#endif
float *values = (float*) malloc( NUM_VALS * sizeof(float));
array_init(values, NUM_VALS);
float *dev_values;
size_t size = NUM_VALS * sizeof(float);
cudaMalloc((void**) &dev_values, size);
cudaMemcpy(dev_values, values, size, cudaMemcpyHostToDevice);
dim3 blocks(BLOCKS,1);
dim3 threads(THREADS,1);
cudaDeviceSynchronize();
#ifdef NAIVE
int j, k;
for (k = 2; k <= NUM_VALS; k <<= 1) {
for (j=k>>1; j>0; j=j>>1) {
bitonicSortNaive<<<blocks, threads>>>(dev_values, j, k);
}
}
#endif
#ifdef ATOMIC
bitonicSortAtomic<<<blocks, threads>>>(dev_values, NUM_VALS);
#endif
cudaDeviceSynchronize();
cudaMemcpy(values, dev_values, size, cudaMemcpyDeviceToHost);
cudaFree(dev_values);
free(values);
}
__ syncblocks_atomic是实现全球障碍的功能。由于存在块间通信,我必须保持数据一致性。
答案 0 :(得分:1)
CUDA编程指南声明:
如果变量位于 全局或共享内存声明为volatile,编译器假定其值 可以随时由另一个线程更改或使用,因此任何引用 此变量编译为实际的内存读或写指令。
这基本上意味着在为变量赋值时会立即刷新内存,并在尝试读取其值时直接从内存中提取(没有缓存)。
在第一个代码示例中,由于A和B都是易失性的,因此会生成6个实际的内存指令。每次使用A或B时都有一个读/写。好的一点是,其他线程可以在制作时更早地看到修改。缺点是执行速度会变慢,因为缓存将被禁用。
在您的第二个代码示例中,另一方面,GPU被授权使用缓存来加速其执行,直到函数结束,当它被强制发出内存写入时。如果A和B都已缓存,则仅发出2次内存写入。缺点是其他线程可能只能在围栏后看到更改后的值。
您应该考虑的另一件事是操作不是原子的。 如果其他线程在执行函数时尝试访问A和B,则在两种情况下都可能会看到函数的部分执行。在第二个代码示例中,这种情况不太可能发生,因为线程可能会使用其缓存值,并立即刷新最终值(无论如何,您不应该依赖它)。
此外,volatile在同一warp的线程中作为__threadfence()的更快版本工作(因为warp中的线程同步)。