1
votes

Comment puis-je définir des valeurs à plusieurs emplacements de mémoire, de manière atomique?

le guide de programmation CUDA dit que toute opération atomique peut être implémentée en utilisant atomicCAS () , et donne un exemple de double add atomique:

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

faire face au problème que:

Je veux écrire une fonction qui peut faire fonctionner deux variables d'adresse atomiquement.

par exemple: atomic ajoute environ deux variables

input

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

result

__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);
}

comment puis-je gérer le problème? merci.


4 commentaires

Qu'y a-t-il de mal à appeler la fonction d'addition atomique deux fois? D'après la description de votre question, les deux opérations sont indépendantes.


voici un exemple simple, en fait dans ce cas faire l'opération deux fois est ok ~ mais dans ma situation, j'ai besoin de la fonction atomique avec deux adresses. avez-vous une suggestion? merci ~


Rien de tel n'existe


Je pense que l'utilisation d'une structure pour stocker les deux variables peut être utile. vous voyez que le double est transféré dans ull, mais je n'ai aucune idée de comment traiter le long long int et la structure non signés.


3 Réponses :


1
votes

Je pense que vous manquez le point de l'opération implémentée ici. En a + = b , l'opération logique est a = a + b , mais avec CAS vous évitez les modifications fallacieuses de a entre sa lecture et sa écrivez. b est utilisé une fois et pas de problème.

Dans a = b + c , aucune des valeurs n'apparaît deux fois, il n'est donc pas nécessaire de se protéger contre les modifications intermédiaires.


2 commentaires

Merci pour votre réponse. Je veux écrire un code atomique qui peut modifier deux variables de manière atomique. avez-vous une suggestion?


merci pour votre clarification ~ maintenant j'ai la solution ~



0
votes

merci à tous les gars répondez-moi! J'ai maintenant la solution. nous pouvons combiner les deux variables dans une structure. ainsi nous pouvons transférer "deux variables avec deux adresses" dans "une structure avec une adresse". voici le code:

x=15000.000000
y=20000

et la solution est

#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;
}

maintenant tout ira bien ~


1 commentaires

1. C'est compliqué. Vous n'avez pas du tout à passer par des doubles. 2. Plus généralement - cela ne fonctionne que puisque la taille totale de la structure est suffisamment petite pour que vous puissiez agir sur elle de manière atomique.



2
votes

En général, vous ne pouvez pas faire cela. Le matériel ne prend pas en charge les modifications atomiques à plusieurs emplacements de la mémoire. Bien que vous puissiez contourner cela si les deux variables sont suffisamment petites pour tenir dans la taille d'une seule opération atomique, cette approche échouera si vous avez plus de 8 octets au total. Vous rencontrerez le "trop ​​de lait" problème.

Une chose que vous pourriez faire est d'avoir une sorte de protocole de synchronisation pour accéder à ces deux valeurs. Par exemple, vous pouvez utiliser un mutex, que seul un thread peut obtenir, pour savoir en toute sécurité que personne d'autre ne change les valeurs pendant que ce thread travaille dessus. Voir: Évitez de prendre trop de temps pour terminer le scénario "trop ​​de lait" .

Bien sûr, cela coûte assez cher dans un réglage GPU. Vous feriez probablement mieux de faire l'une des choses suivantes (en augmentant l'ordre de favorabilité):

  • Utilisez un pointeur ou un index dans un tableau plus grand, et au lieu de changer la structure de manière atomique, changez le pointeur de manière atomique. Cela résout le problème de concurrence, mais ralentit les accès.
  • Modifiez votre algorithme afin que les accès puissent être séparés et ne soient pas obligés de se produire de manière atomique.
  • Modifiez davantage votre algorithme pour éviter que plusieurs threads n'écrivent dans une seule structure de données complexe.

1 commentaires

Merci pour votre clarté ~ Le matériel que vous avez joint m'a vraiment aidé ~