CUDA中的Atomic Saxpy

时间:2017-10-29 14:50:23

标签: cuda mutex atomic

我在CUDA中遇到以下问题。

假设我们有一个索引列表,其中一些或所有索引可以出现多次:

inds = [1, 1, 1, 2, 2, 3, 4]

使用这些索引,我想在float数组x上执行原子saxpy操作(并行)。我并不担心操作的顺序。也就是说,我想这样做,对于花车ak

x[i] = x[i]*a + k;

如果inds中没有重复的索引,这将是微不足道的。

我目前的解决方案(不起作用)是:

// assume all values in adr are greater than or equal to 0.
// also assume a and k are strictly positive.

__device__ inline void atomicSaxpy(float *adr,
                                   const float a,
                                   const float k){

  float old = atomicExch(adr, -1.0f); // first exchange
  float new_;
  if (old <= -1.0f){
    new_ = -1.0f;
  } else {
    new_ = old*a + k;
  }

  while (true) {
    old = atomicExch(adr, new_); // second exchange
    if (old <= -1.0f){
      break;
    }
    new_ = old*a + k;
  }
}

在许多情况下,这似乎会返回正确的答案。

当你没有得到正确的答案时,我认为这是我的想法:

  1. old在第一次交易中获得-1.0f的值。 =&GT; new_ = -1.0f
  2. old也会在第二次交换中获得-1.0f的值。
  3. 该功能退出时没有任何外部影响。
  4. 有一种不同的方法是:

    __device__ inline void atomicSaxpy(float *adr,
                                       const float ia,
                                       const float k){
    
      float val;
    
      while (true) {
        val = atomicExch(adr, -1.0f);
        if (val > 1.0f){
          break;
        }
        atomicExch(adr, val*ia + k);
      }
    }
    

    我的机器上一直死锁。即使是非常简单的输入,例如上面的示例数据。

    是否可以重写此功能以使其正常运行?

    示例答案

    假设所有索引的k=0.1a=0.95以及args的初始值为0.5,结果应为:

    [0.5, 0.7139374999999998, 
     0.6462499999999999, 0.575, 0.575, ...]
    

    我使用Python计算了这些值,它们在CUDA中可能看起来不同。这是算法应该如何表现的一个例子,而不是一个很好的样本集遇到竞争条件问题。

    参考

    这是一个使用atomicAdd实现atomicExch(此时已存在浮点数)的线程:

    https://devtalk.nvidia.com/default/topic/458062/atomicadd-float-float-atomicmul-float-float-/

    示例如下:

    __device__ inline void atomicAdd(float* address, float value) {
      float old = value;  
      float new_old;
    
      do {
        new_old = atomicExch(address, 0.0f);
        new_old += old;
      }
      while ((old = atomicExch(address, new_old)) != 0.0f);
    };
    

    这似乎有点容易,我不太清楚如何适应它。

    其他解决方案

    能够以这种方式解决这个问题对于我的存储器IO问题有几个优点。出于这个原因,我想知道这是否可能。

    一种可能的不同方法是计算每个索引在CPU上出现的次数,然后执行&#34;常规&#34;之后GPU上的saxpy。我假设还有其他可能性,但我仍然对这个特定问题的答案感兴趣。

1 个答案:

答案 0 :(得分:1)

如果这是一个非并行问题,您只需执行此操作:

*adr = *adr * a + k;

由于在adr上运行多个线程,我们应该使用原子操作进行读写。

float adrValue = atomicExch(adr, -1.0f)
float newValue = adrValue * a + k
atomicExch(adr, newValue)

但是,我们必须意识到另一个线程在我们的阅读步骤(ln1)和我们的写入步骤(ln3)之间更新adr的可能性。

所以我们这里的三步操作是非原子的。

为了使它成为原子,我们应该使用compare-and-swap(atomicCAS)来确保我们只有在我们从中读取它的值时才更新内存。我们可以在每次迭代中重复我们的步骤,使用adr中的then-current值作为计算输入,直到step3返回预期的锁定值-1.0f

do {
    float adrValue = atomicExch(adr, -1.0f)
    float newValue = adrValue * a + k
    adrValue = __int_to_float(atomicCAS(adr, 
                                        __float_as_int(-1.0f),
                                        __float_as_int(newValue)))
} while (adrValue != -1.0f)

ps:考虑上面的伪代码