2010-10-27 11 views
8

Sto usando OpenMP e devo usare l'operazione di fetch-and-add. Tuttavia, OpenMP non fornisce una direttiva/chiamata appropriata. Mi piacerebbe preservare la massima portabilità, quindi non voglio fare affidamento sulle intrinseche del compilatore.Fetch-and-add usando le operazioni atomiche di OpenMP

Piuttosto, sto cercando un modo per sfruttare le operazioni atomiche di OpenMP per implementarlo, ma ho raggiunto un punto morto. Questo può essere fatto? N.B., il seguente codice quasi fa quello che voglio:

#pragma omp atomic 
x += a 

Quasi - ma non del tutto, dal momento che ho davvero bisogno il vecchio valore di x. fetch_and_add dovrebbero essere definite per produrre lo stesso risultato come la seguente (solo non bloccabile):

template <typename T> 
T fetch_and_add(volatile T& value, T increment) { 
    T old; 
    #pragma omp critical 
    { 
     old = value; 
     value += increment; 
    } 
    return old; 
} 

(Una domanda equivalente si può chiedere per confrontare-e-swap ma può essere implementato in termini dell'altra, se non mi sbaglio)

+0

solo dire che 'atomic' non è in realtà ciò che il suo nome sembra promettere, dal momento che qualsiasi thread che ha la memoria modificata da un' atomic' (su qualsiasi altro thread) incassato dovrà essere ri-cache. L'atomico così frequente e ripetuto può uccidere la tua performance (meglio usare le serrature e le scritte di buffer race). – Walter

+0

@Walter Questo è anche ciò che ho trovato empiricamente: algoritmo lock-free che si comporta alla pari con l'algoritmo equivalente che utilizza i lock. E l'algoritmo lock-free utilizza una sincronizzazione molto più complessa, non in termini di prestazioni, ma in termini di logica (e quindi opportunità di introdurre bug). –

risposta

4

A partire da openmp 3.1 è disponibile il supporto per l'acquisizione degli aggiornamenti atomici, è possibile acquisire il valore precedente o il nuovo valore. Dal momento che dobbiamo portare il valore dalla memoria per incrementarlo comunque, è logico che dovremmo essere in grado di accedervi da un registro della CPU e inserirlo in una variabile privata del thread.

C'è un bel lavoro intorno se si sta utilizzando GCC (o g ++), cercare builtins atomiche: http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html

E pensare C di Intel/C++ ha anche il supporto per questo, ma non ho provato esso.

Per ora (fino OpenMP 3.1 è implementata), ho usato funzioni wrapper inline in C++ dove si può scegliere la versione da utilizzare in fase di compilazione:

template <class T> 
inline T my_fetch_add(T *ptr, T val) { 
    #ifdef GCC_EXTENSION 
    return __sync_fetch_and_add(ptr, val); 
    #endif 
    #ifdef OPENMP_3_1 
    T t; 
    #pragma omp atomic capture 
    { t = *ptr; *ptr += val; } 
    return t; 
    #endif 
} 

Aggiornamento: Ho appena provato C di Intel ++ Compiler , attualmente ha il supporto per openmp 3.1 (l'acquisizione atomica è implementata).Intel offre l'uso gratuito dei suoi compilatori in linux per scopi non commerciali:

http://software.intel.com/en-us/articles/non-commercial-software-download/

GCC 4.7 supporterà OpenMP 3.1, quando alla fine viene rilasciato ... speriamo presto :)

+0

Ho usato comunque le integrazioni GCC, ma ovviamente questo è orribile per l'interoperabilità. Grazie per il puntatore OpenMP 3.1. Sfortunatamente, dal momento che VC++ attualmente non supporta nemmeno OpenMP 3, questo è piuttosto teorico per il momento. –

+1

Solo per il completamento: dovrebbe essere '#ifdef __GNUC__' ...' #elif definito (_OPENMP) e _OPENMP> = 201107' (per OpenMP 3.1) ... '#else #error" Richiede gcc o OpenMP> = 3.1 "# endif'. Grazie! – eudoxos

2

Se si desidera ottenere vecchio valore di x e a non è cambiata, l'uso (XA) come vecchio valore:.

fetch_and_add(int *x, int a) { 
#pragma omp atomic 
*x += a; 

return (*x-a); 
} 

UPDATE: non è stato davvero una risposta , perché x può essere modificato dopo l'atomico da un altro thread. Quindi sembra impossibile rendere universale "Fetch-and-add" usando i Pragma OMP. Come universale intendo il funzionamento, che può essere facilmente utilizzato da qualsiasi punto del codice OMP.

È possibile utilizzare omp_*_lock funzioni per simulare un Atomics:

typedef struct {omp_lock_t serratura; valore int;} atomic_simulated_t;

fetch_and_add(atomic_simulated_t *x, int a) 
{ 
    int ret; 
    omp_set_lock(x->lock); 
    x->value +=a; 
    ret = x->value; 
    omp_unset_lock(x->lock); 
} 

Questo è brutto e lento (facendo 2 operazioni atomiche anziché 1). Ma se vuoi che il tuo codice sia molto portatile, non sarà il più veloce in tutti i casi.

Si dice "come segue (solo non bloccante)". Ma qual è la differenza tra le operazioni "non bloccanti" (utilizzando il prefisso "LOCK" della CPU, o LL/SC o ecc.) E le operazioni di blocco (che sono implementate con diverse istruzioni atomiche, ciclo occupato per attesa breve dello sblocco e sospensione del sistema operativo) per lunghe attese)?

+0

E per cas - openmp supporta una variante di atomico condizionale, ma solo in fortran. È un MIN e MAX; sono condizionali. Può essere utilizzato per implementare un sottoinsieme di operazioni CAS. – osgx

+0

Duh. Mi sento un po 'stupido ora. –

+0

@Konrad Rudolph, anche io, perché mi ci vuole 1 settimana per ottenere questo :). Inoltre, il passaggio richiesto per me era un'operazione di apprendimento LL/SC su piattaforme diverse. – osgx

Problemi correlati