2011-06-23 19 views
8

Me he dado cuenta de que recibo un gran golpe de rendimiento cuando tengo un algoritmo que bloquea y desbloquea un subproceso ALOT.Rendimiento de pthread_mutex_lock/unlock

¿Hay alguna manera de ayudar a esta sobrecarga? ¿Sería el uso de un semáforo más o menos eficiente?

Gracias

typedef struct _treenode{ 
    struct _treenode *leftNode; 
    struct _treenode *rightNode; 
    int32_t data; 
    pthread_mutex_t mutex; 
}TreeNode; 

pthread_mutex_t _initMutex = PTHREAD_MUTEX_INITIALIZER; 

int32_t insertNode(TreeNode **_trunk, int32_t data){ 
    TreeNode **current; 
    pthread_mutex_t *parentMutex = NULL, *currentMutex = &_initMutex; 

    if(_trunk != NULL){ 
     current = _trunk; 
     while(*current != NULL){ 
     pthread_mutex_lock(&(*current)->mutex); 
     currentMutex = &(*current)->mutex; 
     if((*current)->data < data){ 
      if(parentMutex != NULL) 
       pthread_mutex_unlock(parentMutex); 
      pthreadMutex = currentMutex; 
      current = &(*current)->rightNode; 
     }else if((*current)->data > data){ 
      if(parentMutex != NULL) 
       pthread_mutex_unlock(parentMutex); 
      parentMutex = currentMutex; 
      current = &(*current)->leftNode; 
     }else{ 
      pthread_mutex_unlock(currentMutex); 
      if(parentMutex != NULL) 
       pthread_mutex_unlock(parentMutex); 
      return 0; 
     } 
     } 
     *current = malloc(sizeof(TreeNode)); 
     pthread_mutex_init(&(*current)->mutex, NULL); 
     pthread_mutex_lock(&(*current)->mutex); 
     (*current)->leftNode = NULL; 
     (*current)->rightNode = NULL; 
     (*current)->data = data; 
     pthread_mutex_unlock(&(*current)->mutex); 
     pthread_mutex_unlock(currentMutex); 
    }else{ 
     return 1; 
    } 
    return 0; 
} 

int main(){ 
    int i; 
    TreeNode *trunk = NULL; 
    for(i=0; i<1000000; i++){ 
     insertNode(&trunk, rand() % 50000); 
    } 
} 
+3

Un semáforo hace cosas diferentes (más complejas) y es más probable que sea más lento. ¿Cuál es tu sistema operativo? ¿Puedes hacer que la cerradura sea más fina para que no bloquees durante tanto tiempo? –

+3

O hágalos de grano más grueso/haga más trabajo por bloqueo, para que no tenga tantos cambios de contexto. Hay un buen equilibrio. – nos

+1

Si muestra/describe el algoritmo, podemos dar pistas. La solución debería ser: utilizar menos bloqueo (dividir el trabajo en núcleos dedicados, por lo que no es necesario bloquear las subregiones) o hacerlo sin bloqueo (haaaaaaard). Nada más que Moores Law va a ayudar – sehe

Respuesta

14

En lugar de preocuparse por las briznas de hierba, retroceda y observe todo el bosque.

Cualquier algoritmo que dependa de dos subprocesos que puedan pisar estrechamente los dedos de los demás es inherentemente ineficiente. Intenta encontrar una forma de reducir drásticamente la necesidad de interacción.

Por ejemplo, si un hilo produce datos y el otro lo consume, uno puede pensar fácilmente en un algoritmo ineficiente donde el productor publica los datos en la memoria compartida y luego espera que el otro los consuma. Mientras tanto, el consumidor está esperando que el productor termine, etc., etc. Todo esto se simplifica mucho cuando el productor escribe en un archivo o tubería, y el consumidor lo lee.

0

bloqueo y desbloqueo de las operaciones son muy caros en el caso de pthread_mutex_lock/desbloqueo. Con más detalles sobre el algoritmo, podría hacer algunas sugerencias, pero por lo que puedo decir, no puedo decir nada con seguridad. Los semáforos son una alternativa (de nuevo dependiendo del algoritmo) y también las barreras son otro método útil para la concurrencia. Para ayudar a la sobrecarga, puede hacer cosas como hacer que sus bloqueos tengan una granularidad más pequeña o una granularidad mayor. bloqueos dentro de los bucles que se repiten varias veces son una mala idea y es posible que desee moverlos fuera del bucle. Este es solo un ejemplo, pero probablemente haya más que se me ocurra. Se trata de determinar si el costo del bloqueo es mayor que el de la sección crítica de tu código. Si proporciona su algoritmo o algún código de muestra, me encantaría echarle un vistazo.

+0

Mencionó pthread_mutex_lock/unlock, que son bastante caros, aunque es correcto que debería editar mi respuesta para abarcar solo pthread_mutex_lock/unlock ya que CriticalSection es relativamente rápido, como lo son los bloqueos de Boost. También sugerí que publicara algunos códigos y algunas cosas que podría hacer para cambiar la sección bloqueada y mejorar el rendimiento. –

11

pthread_mutex_lock y pthread_mutex_unlock varían en costo dependiendo de la discordia:

  1. uso solo hilo - ya sea sólo existe un hilo, o sólo se está utilizando la exclusión mutua y el recurso que protege: bloqueo es prácticamente libre , tal vez 80-100 ciclos como máximo.
  2. Múltiples hilos usando el recurso, pero los bloqueos se mantienen por intervalos muy cortos y la contención es rara: el bloqueo tiene algún costo, y es difícil de medir; el costo consiste principalmente en invalidar las líneas de caché de otros núcleos '/ cpus'.
  3. Conflicto significativo de bloqueo: casi todas las operaciones de bloqueo y desbloqueo requerirán la asistencia del kernel, y el costo es fácilmente varios miles (posiblemente incluso decenas de miles) de ciclos por bloqueo/desbloqueo.

Aún así, los mutexes deberían ser la primitiva de bloqueo menos costosa en la mayoría de las situaciones y en la mayoría de las implementaciones. Ocasionalmente los spinlocks pueden tener un mejor rendimiento. Nunca esperaría que los semáforos funcionaran mejor.

+4

En algunos contextos, 80-100 ciclos no es "prácticamente gratis". – Michael

+0

Quizás debería aclarar: lo estaba comparando con un par de llamadas a funciones externas triviales, es decirel rendimiento que obtendría si 'pthread_mutex_lock' y' pthread_mutex_unlock' fuesen funciones casi vacías (pero aún no pueden ser inline y aún así establecer un marco de pila). No tengo cifras delante de mí, pero creo que el caso de "bloqueos sin operación" se acercará a 80 ciclos, excepto tal vez en las máquinas de gama alta x86. –

+0

Para un productor/consumidor simple basado en un anillo atómico, una señalización de semáforo cuando hay datos disponibles puede superar una variable/mutex de condición, ya que para estos últimos los productores también deben bloquear para cambiar la condición. – CashCow

6

Por lo que puedo ver, su estrategia de bloqueo no es óptima ya que la mayoría de los bloqueos no se tomarán para cambiar los datos, sino solo para leer y encontrar el camino a través del árbol.

pthread_rwlock_tpodría ayuda en esto. Solo tomaría bloqueos de lectura en la ruta hacia abajo en el árbol hasta que llegue a un nodo donde desea hacer algunas modificaciones. Allí tomarías un bloqueo de escritura.Con eso, podrías tener otros hilos para realizar la misma tarea cuando camines por el árbol en una rama diferente sin molestar a los demás.

Una implementación decente de pthread_rwlock_t haría esto con un contador para los lectores que cambie con operaciones atómicas, siempre y cuando no haya conflicto con los escritores. Esto debería ser muy rápido. Una vez que haya una disputa, sería tan costoso como un mutex, creo.

+0

¿Dónde se define rwlock_t? Busqué spinlock.h, pero sin éxito ... – Andrew

+0

@Andrew, lo siento es 'pthread_rwlock_t'. Y debería estar allí en '

+0

@Andrew y Jens. 1) RWLocks no siempre son una mejor solución. Si todas las operaciones de lectura toman muy poco tiempo y todas las operaciones de escritura toman mucho tiempo, su sobrecarga en comparación con MutEx puede matar fácilmente su beneficio conceptual. Depende de la cantidad de lectores y escritores y de la frecuencia con la que llegan a la sección en paralelo. 2) Hasta donde yo sé, RWLocks está construido sobre MutExes, pero incluso si no, son más caros que MuitExes. 3) Usaría un solo RWLock para todo el árbol si la escritura ocurre raramente. Entonces, todos los lectores pueden ser felices en paralelo. –

0

Sus cerraduras son probablemente demasiado finas. Por supuesto, la granularidad óptima puede variar según la carga de trabajo.

Se puede usar un candado para todo el árbol, y puede obtener mejores resultados. Pero, si haces muchas lecturas y relativamente pocas inserciones/eliminaciones, terminas con todo el árbol bloqueado a menudo sin una buena razón. Es posible que desee utilizar un bloqueo lector-escritor, que permitiría varios lectores al mismo tiempo.

Su pregunta me recordó a this other one, cuando hay una comparación entre el bloqueo de grano fino y el bloqueo de grano grueso para una lista vinculada. Mientras que en la versión de grano grueso cada hilo se ejecutaba por turno (no en paralelo), y el tiempo total de ejecución era ligeramente mayor que la suma del tiempo de ejecución de cada hilo, y en la versión fina el tiempo de ejecución total era mucho menor que el suma del tiempo de ejecución de cada hilo, la sobrecarga adicional de bloqueo de grano fino compensó totalmente estos beneficios, haciendo que la versión de grano fino sea más lenta que la de grano grueso.