CUDA易失性和threadfence

时间:2013-08-09 20:04:45

标签: cuda shared-memory volatile comparator

以下两个功能有什么区别?

__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是实现全球障碍的功能。由于存在块间通信,我必须保持数据一致性。

1 个答案:

答案 0 :(得分:1)

CUDA编程指南声明:

  

如果变量位于   全局或共享内存声明为volatile,编译器假定其值   可以随时由另一个线程更改或使用,因此任何引用   此变量编译为实际的内存读或写指令。

这基本上意味着在为变量赋值时会立即刷新内存,并在尝试读取其值时直接从内存中提取(没有缓存)。

在第一个代码示例中,由于A和B都是易失性的,因此会生成6个实际的内存指令。每次使用A或B时都有一个读/写。好的一点是,其他线程可以在制作时更早地看到修改。缺点是执行速度会变慢,因为缓存将被禁用。

在您的第二个代码示例中,另一方面,GPU被授权使用缓存来加速其执行,直到函数结束,当它被强制发出内存写入时。如果A和B都已缓存,则仅发出2次内存写入。缺点是其他线程可能只能在围栏后看到更改后的值。

您应该考虑的另一件事是操作不是原子的。 如果其他线程在执行函数时尝试访问A和B,则在两种情况下都可能会看到函数的部分执行。在第二个代码示例中,这种情况不太可能发生,因为线程可能会使用其缓存值,并立即刷新最终值(无论如何,您不应该依赖它)。

此外,volatile在同一warp的线程中作为__threadfence()的更快版本工作(因为warp中的线程同步)。