2010-10-27 9 views
8

Estoy usando OpenMP y necesito usar la operación de búsqueda y agregación. Sin embargo, OpenMP no proporciona una directiva/llamada apropiada. Me gustaría preservar la máxima portabilidad, por lo tanto, no quiero confiar en los intrínsecos del compilador.Fetch-and-add usando las operaciones atómicas de OpenMP

Más bien, estoy buscando una manera de aprovechar las operaciones atómicas de OpenMP para implementar esto, pero he llegado a un callejón sin salida. ¿Puede esto siquiera estar terminado? N. B., el siguiente código casi hace lo que yo quiero:

#pragma omp atomic 
x += a 

Casi - pero no del todo, ya que realmente necesito el antiguo valor de x. fetch_and_add deberían definirse para producir el mismo resultado que el siguiente (sólo sin bloqueo):

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

(Una cuestión equivalentes se les puede pedir para-y-canje de comparar, pero uno puede ser implementada en términos de la otra, si no me equivoco)

+0

sólo para decir que 'atomic' no es realmente lo que su nombre parece prometer, ya que cualquier hilo que tenga la memoria modificada por un' atomic' (en cualquier otro hilo) deberá cambiarse a caché. Tan frecuente y repetido 'atómico' puede matar su rendimiento (mejor uso de bloqueos y escritura de búfer de carreras). – Walter

+0

@Walter Eso también es lo que encontré empíricamente: algoritmo sin bloqueo que funciona justo a la par con el algoritmo equivalente que usa bloqueos. Y el algoritmo sin bloqueo utiliza una sincronización mucho más compleja, no en términos de rendimiento sino en términos de lógica (y por lo tanto, oportunidades para introducir errores). –

Respuesta

4

Desde openmp 3.1 hay soporte para capturar actualizaciones atómicas, puede capturar el valor anterior o el nuevo. Como tenemos que traer el valor de la memoria para incrementarlo de todos modos, solo tiene sentido que podamos acceder desde, digamos, un registro de CPU y ponerlo en una variable privada de subprocesos.

Hay un buen trabajo en torno a si se está utilizando gcc (o g ++), mira hacia arriba órdenes internas atómicas: http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html

Se piensa C de Intel/C++ también tiene soporte para esto, pero no lo he probado eso.

Por ahora (hasta OpenMP 3.1 se implementa), he utilizado funciones de contenedor en línea en C++ donde se puede elegir qué versión se utilizará en tiempo de compilación:

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 
} 

Actualización: Acabo de intentar C de Intel ++ compilador , actualmente tiene soporte para openmp 3.1 (se implementa la captura atómica).Intel ofrece el uso gratuito de sus compiladores en Linux para propósitos no comerciales:

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

GCC 4.7 apoyará OpenMP 3.1, cuando finalmente se libera ... esperemos que pronto :)

+0

He recurrido al uso de construcciones integradas de GCC de todos modos, pero por supuesto, esto es horrible para la interoperabilidad. Gracias por el puntero de OpenMP 3.1. Desafortunadamente, dado que VC++ actualmente ni siquiera admite OpenMP 3, esto es bastante teórico por el momento. –

+1

Solo para completar: debe ser '#ifdef __GNUC__' ...' #elif definido (_OPENMP) y _OPENMP> = 201107' (para OpenMP 3.1) ... '#else #error" Requiere gcc o OpenMP> = 3.1 "# endif'. ¡Gracias! – eudoxos

2

Si desea obtener antiguo valor de x y no se cambia, el uso (Xa) como valor antiguo:.

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

return (*x-a); 
} 

ACTUALIZACIÓN: no era realmente una respuesta , porque x puede ser modificado después de atómico por otro hilo. Parece que es imposible hacer un "Fetch-and-add" universal usando OMP Pragmas. Como universal quiero decir operación, que se puede usar fácilmente desde cualquier lugar del código OMP.

Puede utilizar omp_*_lock funciones para simular un atómica:

typedef struct {omp_lock_t bloqueo; int value;} 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); 
} 

Esto es feo y lento (haciendo 2 operaciones atómicas en lugar de 1). Pero si desea que su código sea muy portátil, no será el más rápido en todos los casos.

Dice "como lo siguiente (solo sin bloqueo)". Pero cuál es la diferencia entre las operaciones "sin bloqueo" (usando el prefijo "LOCK" de la CPU, o LL/SC o etc.) y las operaciones de bloqueo (que se implementan con varias instrucciones atómicas, bucle ocupado para una espera corta de desbloqueo y SO durmiendo para largas esperas)?

+0

Y para cas - openmp admite una variante de atómico condicional, pero solo en fortran. Es un MIN y MAX; ellos son condicionales Se puede usar para implementar un subconjunto de operaciones CAS. – osgx

+0

Duh. Me siento un poco estúpido ahora. –

+0

@Konrad Rudolph, yo también, porque me lleva 1 semana obtener esto :). Además, el paso requerido para mí fue una operación de LL/SC de aprendizaje en diferentes plataformas. – osgx

Cuestiones relacionadas