2010-07-26 12 views
7

Tengo objetos de función pequeños pero de uso frecuente. Cada hilo obtiene su propia copia. Todo está asignado estáticamente. Las copias no comparten datos globales o estáticos. ¿Debo proteger estos objetos del intercambio falso?Variables de pila y pila falsas

Gracias. EDITAR: Aquí hay un programa de juguete que usa Boost.Threads. ¿Puede producirse el intercambio falso para el campo data?

#include <boost/thread/thread.hpp> 

struct Work { 
    void operator()() { 
     ++data; 
    } 

    int data; 
}; 

int main() { 
    boost::thread_group threads; 
    for (int i = 0; i < 10; ++i) 
     threads.create_thread(Work()); 
    threads.join_all(); 
} 
+0

El código funcionaría mejor. Si los objetos de su función tienen datos 'estáticos', entonces todos los hilos compartirán esos datos. – GManNickG

+0

Creo que necesita decir exactamente lo que quiere decir con "cada hilo obtiene su propia copia" y "asignada estáticamente". ¿Los hilos usan la copia de los demás? – Elemental

+0

@Elemental: algunos compiladores pueden usar el almacenamiento local TLS-thread. Esto significa que puede asignar estáticamente AND thread-safe, aunque es lento. – Puppy

Respuesta

6

Falso intercambio entre hilos es cuando 2 o más hilos utilizan la misma línea de caché.

E.g. :

struct Work { 
    Work(int& d) : data(d) {} 
    void operator()() { 
     ++data; 
    } 

    int& data; 
}; 

int main() { 
    int false_sharing[10] = { 0 }; 
    boost::thread_group threads; 
    for (int i = 0; i < 10; ++i) 
     threads.create_thread(Work(false_sharing[i])); 
    threads.join_all(); 

    int no_false_sharing[10 * CACHELINE_SIZE_INTS] = { 0 }; 
    for (int i = 0; i < 10; ++i) 
     threads.create_thread(Work(no_false_sharing[i * CACHELINE_SIZE_INTS])); 
    threads.join_all(); 
} 

Los hilos en el primer bloque sufren de uso compartido falso. Los hilos en el segundo bloque no (gracias a CACHELINE_SIZE).

Los datos en la pila siempre están "lejos" de otros hilos. (Por ejemplo, en Windows, al menos un par de páginas).

Con su definición de objeto de función, puede aparecer el uso compartido falso, porque las instancias de Work se crean en el montón y este espacio de montón se utiliza dentro del subproceso.

Esto puede provocar que varias instancias Work sean adyacentes y, por lo tanto, puede incurrir en el intercambio de líneas de caché.

Pero ... su muestra no tiene sentido, porque los datos nunca se tocan fuera, por lo que el intercambio falso se produce de forma innecesaria.

La manera más fácil, para evitar problemas como este, es copiar los datos 'compartidos' localmente en la pila y luego trabajar en la copia de la pila. Cuando su trabajo finalice, cópielo de nuevo a la var. De salida.

por ejemplo:

struct Work { 
    Work(int& d) : data(d) {} 
    void operator()() 
    { 
     int tmp = data; 
     for(int i = 0; i < lengthy_op; ++i) 
      ++tmp; 
     data = tmp; 
    } 

    int& data; 
}; 

Esto evita todos los problemas con el uso compartido.

+0

¿Está diciendo que los datos pueden verse afectados por el uso compartido falso? En mi caso, copiarlo en la pila de la función no ayudará, porque la función en sí misma se debe llamar con frecuencia y usa datos solo una vez por llamada. – user401947

+0

Cuando se debe llamar a la función con mucha frecuencia, no tiene sentido crear un hilo cada vez. O trabajas mucho en un hilo nuevo, o simplemente quemas ciclos para la creación/destrucción del hilo. Y en el caso posterior, eclipsa el costo del intercambio falso por los enormes costos de los hilos. – Christopher

+0

Sin embargo. Si no puede copiar los datos en la pila para su operación, simplemente haga que 'Work' sea lo suficientemente grande como para que tenga al menos CACHLINE_SIZE de longitud. Pierdes un par de bytes, pero puedes estar seguro de que nunca te encontrarás con problemas de compartición falsos. – Christopher

0

I' no se siente del todo seguro con los detalles, pero aquí está mi opinión:

(1) Su ejemplo simplificado se rompe desde el impulso create_thread espera una referencia, se pasa un temporal.

(2) si utiliza vector<Work> con un elemento para cada hilo, o bien, los tiene en memoria secuencialmente, se producirá el intercambio falso.

+0

(1) No, no está roto. create_thread acepta su argumento por valor. Verifique la declaración si no me cree. (2) He indicado claramente que cada hilo obtiene su propia copia. Verifica el código. El objeto de función se pasa por valor. – user401947

+0

Lo es. El trabajo no se copia en la pila objetivo. Se 'actualiza' en el contexto de 'create_thread' y solo se transfiere un puntero (compartido) a la pila objetivo. Ahí los datos solo son referenciados por un puntero. (Probé esto asignando thread_id al miembro de datos y luego mirando el valor en la llamada al operador()) – Christopher

+0

(1) estamos hablando de: 'thread * create_thread (const boost :: function0 & threadfunc); '? Eso es lo que encontré al intentar verificar la referencia – peterchen

2

Hice un poco de investigación y parece que no hay una solución milagrosa para compartir falsamente. Esto es lo que se me ocurre (gracias a Christopher): 1) Rellene sus datos desde ambos lados con cosas no usadas o usadas con menos frecuencia. 2) Copie sus datos en la pila y cópielos nuevamente después de que todo el trabajo duro haya terminado. 3) Use la asignación de memoria alineada con el caché.