CUDA原子和非原子内存访问

时间:2012-10-26 10:39:51

标签: cuda malloc free semaphore atomic

我有两个CUDA函数可以操作全局内存中的链表。函数pmalloc删除其中一个列表的head元素。它首先选择一个列表,然后调用实际删除head元素的pmallocBucket。如果所选列表为空,pmalloc将尝试其他列表。另一方面,pfree函数会将新的head元素插入到列表中。

通过信号量实现相互排斥,每个链表都有一个信号量。信号量的实现来自 CUDA By Example 一书。在其他一些测试代码中,信号量工作正常。

我对代码的问题如下:有时,多个线程会尝试同时访问同一个链表。这些访问由信号量成功地序列化,但有时,线程将从列表中删除与前一个线程相同的头元素。这可能会立即连续发生,或者中间可能存在一个或多个其他线程。然后该线程将free一个未分配的内存区域,我的程序崩溃。

以下是上述功能。 mmd是全局内存中的一个结构,它是从另一个函数初始化的。

extern __device__ void wait(int* s) {
  while(atomicCAS(s, 0, 1) != 0);
}

extern __device__ void signal(int* s) {
  atomicExch(s, 0);
}

__device__ void pfree(Expression* node) {
  LinkedList* l = (LinkedList*) malloc(sizeof(LinkedList));
  l->cell = node;
  node->type = EMPTY;
  node->funcidx = 0;
  node->name = NULL;
  node->len = 0;
  node->value = 0;
  node->numParams = 0;
  free(node->params);

  int targetBin = (blockIdx.x * mmd.bucketSize + threadIdx.x) / BINSIZE;
  /*
   * The for loop and subsequent if are necessary to make sure that only one
   * thread in a warp is actively waiting for the lock on the semaphore.
   * Leaving this out will result in massive headaches.
   * See "CUDA by example", p. 273
   */

  for(int i = 0; i < WARPSIZE; i++) {
    if(((threadIdx.x + blockIdx.x * blockDim.x) % WARPSIZE) == i) {
      wait(&mmd.bucketSemaphores[targetBin]);
        l->next = mmd.freeCells[targetBin];
        mmd.freeCells[targetBin] = l;
      signal(&mmd.bucketSemaphores[targetBin]);
    }
  }
}

__device__ Expression* pmalloc() {
  Expression* retval = NULL;
  int i = 0;

  int bucket = (blockIdx.x * mmd.bucketSize + threadIdx.x) / BINSIZE;

  while(retval == NULL && i < mmd.numCellBins) {
    retval = pmallocBucket((i + bucket) % mmd.numCellBins);
    i++;
  }

  if(retval == NULL) {
    printf("(%u, %u) Out of memory\n", blockIdx.x, threadIdx.x);
  }

  return retval;
}

__device__ Expression* pmallocBucket(int bucket) {
  Expression* retval = NULL;

  if(bucket < mmd.numCellBins) {
    LinkedList* l = NULL;

    for(int i = 0; i < WARPSIZE; i++) {
      if(((threadIdx.x + blockIdx.x * blockDim.x) % WARPSIZE) == i) {
        wait(&mmd.bucketSemaphores[bucket]);
          l = mmd.freeCells[bucket];

          if(l != NULL) {
            retval = l->cell;
            mmd.freeCells[bucket] = l->next;
          }
        signal(&mmd.bucketSemaphores[bucket]);
        free(l);
      }
    }
  }

  return retval;
}

我很茫然。我不知道实际上出了什么问题,到目前为止,我所有的尝试都是无法解决的。非常感谢任何帮助。

P上。 S。:是的,我确实意识到原子操作和信号量的使用对于CUDA应用来说并不理想。但在这种情况下,到目前为止我还不知道如何以不同的方式实现这一点,而且我的项目处于一个绝对固定的截止日期,接近真正的快速,所以这将是必须的。

1 个答案:

答案 0 :(得分:1)

您需要确保在信号量受保护的关键部分内完全执行列表操作,而不使用获取信号量之前的过时数据。

l->nextmmd.freeCells声明为volatile,或通过原子函数(atomicExch())对其进行操作。

或者,您可以使用具有合适缓存运算符的内联汇编。使用mov.cg进行加载应该足以确保在__threadfence()之前没有使用本地缓存的值以及signal(),以确保在信号量到达之前写入已达到全局内存释放。确保使用asm volatile(...),或者再次使用编译器可以将整个内联asm移出临界区。