【问题标题】:How can I set values at multiple memory locations, atomically?如何以原子方式在多个内存位置设置值?
【发布时间】:2019-06-06 02:34:30
【问题描述】:

CUDA编程指南说任何原子操作都可以使用atomicCAS()来实现,并给出了原子双加的例子:

__device__ float single(double *address,double val)
{
unsigned long long int *address_as_ull =(unsigned long long int*)address;
unsigned long long int assumed;
unsigned long long int old = *address_as_ull;

do
{
    assumed = old;
    old = atomicCAS(address_as_ull,assumed,__double_as_longlong(val + __longlong_as_double(assumed)));
}while(assumed !=old);
   return __longlong_as_double(old);
}

现在,我面临的问题是:

我想写一个可以原子操作两个变量地址的函数。

例如: 原子添加大约两个变量

输入

double *address_1, int *address_2
double val_1,int val_2

结果

*address_1 = *address_1+val_1;
*address_2 = *address_2+val_2;

我该如何处理这个问题?谢谢。

【问题讨论】:

  • 两次调用原子加法函数有什么问题?从您问题中的描述来看,这两个操作是独立的。
  • 这里是一个简单的例子,实际上在这种情况下做两次操作是可以的~但在我的情况下,我需要两个地址的原子函数。你有什么建议吗?谢谢~
  • 没有这样的东西
  • 我认为使用结构来存储这两个变量可能会有所帮助。你看到 double 被转移到 ull,但我不知道如何处理 unsigned long long int 和结构。

标签: cuda atomic


【解决方案1】:

一般来说,您不能这样做。硬件支持对内存中多个位置的原子更改。如果两个变量都足够小以适合单个原子操作的大小,则可以避免这种情况-如果总体上超过 8 个字节,则此方法将失败。你会遇到"too much milk"的问题。

您可以做的一件事是使用某种同步协议来访问这两个值。例如,您可以使用只有一个线程可以获得的互斥锁,以安全地知道在该线程处理这些值时没有其他人正在更改这些值。见:Avoid taking a long time to finish the 'too much milk' scenario

当然,这在 GPU 设置中是相当昂贵的。您可能最好执行以下操作之一(通过增加好感度):

  • 使用指向更大数组的指针或索引,而不是原子地更改结构,而是原子地切换指针。这解决了并发问题,但会降低访问速度。
  • 更改您的算法,以便可以分离访问,而不必以原子方式进行。
  • 进一步更改您的算法,以避免多个线程写入单个复杂数据结构。

【讨论】:

  • 感谢您的清晰说明~您所附的材料对我很有帮助~
【解决方案2】:

我认为您错过了此处实施的操作的重点。在a+=b 中,逻辑操作是a = a + b,但使用CAS 可以避免在读取和写入之间对a 进行虚假更改。 b 使用一次,没有问题。

a = b + c 中,没有任何值出现两次,因此无需防止其间发生任何变化。

【讨论】:

  • 感谢您的回答。我想编写一个可以原子地修改两个变量的原子代码。你有什么建议吗?
  • 谢谢你的澄清~现在我有解决办法了~
【解决方案3】:

感谢所有回复我的人! 我现在有了解决方案。 我们可以将这两个变量组合成一个结构。所以我们可以将“两个变量,两个地址”转换为“一个结构,一个地址”。这是代码:

#include <stdio.h>
struct pair_t
{
    float x;
    int y;
};

__device__ float single(double *address,double val)
{   

    unsigned long long int *address_as_ull =(unsigned long long int*)address;
    unsigned long long int assumed;
    unsigned long long int old = *address_as_ull;

    do
    {
        assumed = old;
        old = atomicCAS(address_as_ull,assumed,__double_as_longlong(val + __longlong_as_double(assumed)));
    }while(assumed !=old);
    return __longlong_as_double(old);
}



__device__ void myadd(pair_t *address, double val_1 ,int val_2)
{   
    union myunion
    {  
        pair_t p;
        unsigned long long int ull;
    };

    unsigned long long int *address_as_ull;
    address_as_ull = (unsigned long long int *)address;

    union myunion assumed;
    union myunion old_value;
    union myunion new_value;

    old_value.p = *(pair_t *)address_as_ull;

    do
    {
        assumed = old_value;
        // cirtical area begin--------------------
        new_value.p.x = assumed.p.x+val_1;
        new_value.p.y = assumed.p.y+val_2;
        // cirtical area end----------------------

        old_value.ull = atomicCAS(address_as_ull,assumed.ull,new_value.ull);
    }while(assumed.ull !=old_value.ull);
}


__global__ void kernel (pair_t *p)
{
    myadd(p,1.5,2);
}

int main()
{
    pair_t p;
    p.x=0;
    p.y=0;
    pair_t *d_p = NULL;
    cudaMalloc((pair_t **)&d_p, sizeof(pair_t));
    cudaMemcpy(d_p, &p, sizeof(pair_t), cudaMemcpyHostToDevice);

    kernel<<<100, 100>>>(d_p);

    cudaMemcpy(&p, d_p, sizeof(pair_t), cudaMemcpyDeviceToHost);

    cudaDeviceSynchronize();
    printf("x=%lf\n", p.x);
    printf("y=%d\n", p.y);
    cudaDeviceReset();
    return 0;
}

解决办法是

x=15000.000000
y=20000

现在一切都会好的~

【讨论】:

  • 1.这是令人费解的。你根本不必经历双打。 2. 更一般地说 - 这仅适用于结构的总大小足够小,您可以原子地对其进行操作。
猜你喜欢
  • 1970-01-01
  • 2016-04-01
  • 2016-02-29
  • 2013-04-17
  • 2021-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多