Pregunta

Yo quería saber lo que sería mejor / más rápido utilizar las llamadas POSIX como pthread_once() y sem_wait() o la dispatch_ * funciones, así que creé una pequeña prueba y estoy sorprendido por los resultados (preguntas y los resultados se encuentran al final).

En el código de prueba que estoy usando mach_absolute_time () medir el tiempo de las llamadas. Realmente no importa que esto no es exactamente coincidente con nano-segundos; Estoy comparando los valores entre sí por lo que las unidades de tiempo exacto no importa, sólo las diferencias entre el intervalo hacen. Los números en la sección de resultados son repetibles y no promediado; Podría haber un promedio de las veces, pero no estoy en busca de números exactos.

test.m (aplicación de consola simple, fácil compilar):

#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
#include <semaphore.h>
#include <pthread.h>
#include <time.h>
#include <mach/mach_time.h>  

// *sigh* OSX does not have pthread_barrier (you can ignore the pthread_barrier 
// code, the interesting stuff is lower)
typedef int pthread_barrierattr_t;
typedef struct
{
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    int count;
    int tripCount;
} pthread_barrier_t;


int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count)
{
    if(count == 0)
    {
        errno = EINVAL;
        return -1;
    }
    if(pthread_mutex_init(&barrier->mutex, 0) < 0)
    {
        return -1;
    }
    if(pthread_cond_init(&barrier->cond, 0) < 0)
    {
        pthread_mutex_destroy(&barrier->mutex);
        return -1;
    }
    barrier->tripCount = count;
    barrier->count = 0;

    return 0;
}

int pthread_barrier_destroy(pthread_barrier_t *barrier)
{
    pthread_cond_destroy(&barrier->cond);
    pthread_mutex_destroy(&barrier->mutex);
    return 0;
}

int pthread_barrier_wait(pthread_barrier_t *barrier)
{
    pthread_mutex_lock(&barrier->mutex);
    ++(barrier->count);
    if(barrier->count >= barrier->tripCount)
    {
        barrier->count = 0;
        pthread_cond_broadcast(&barrier->cond);
        pthread_mutex_unlock(&barrier->mutex);
        return 1;
    }
    else
    {
        pthread_cond_wait(&barrier->cond, &(barrier->mutex));
        pthread_mutex_unlock(&barrier->mutex);
        return 0;
    }
}

//
// ok you can start paying attention now...
//

void onceFunction(void)
{
}

@interface SemaphoreTester : NSObject
{
    sem_t *sem1;
    sem_t *sem2;
    pthread_barrier_t *startBarrier;
    pthread_barrier_t *finishBarrier;
}
@property (nonatomic, assign) sem_t *sem1;
@property (nonatomic, assign) sem_t *sem2;
@property (nonatomic, assign) pthread_barrier_t *startBarrier;
@property (nonatomic, assign) pthread_barrier_t *finishBarrier;
@end
@implementation SemaphoreTester
@synthesize sem1, sem2, startBarrier, finishBarrier;
- (void)thread1
{
    pthread_barrier_wait(startBarrier);
    for(int i = 0; i < 100000; i++)
    {
        sem_wait(sem1);
        sem_post(sem2);
    }
    pthread_barrier_wait(finishBarrier);
}

- (void)thread2
{
    pthread_barrier_wait(startBarrier);
    for(int i = 0; i < 100000; i++)
    {
        sem_wait(sem2);
        sem_post(sem1);
    }
    pthread_barrier_wait(finishBarrier);
}
@end


int main (int argc, const char * argv[]) 
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int64_t start;
    int64_t stop;

    // semaphore non contention test
    {
        // grrr, OSX doesn't have sem_init
        sem_t *sem1 = sem_open("sem1", O_CREAT, 0777, 0);

        start = mach_absolute_time();
        for(int i = 0; i < 100000; i++)
        {
            sem_post(sem1);
            sem_wait(sem1);
        }
        stop = mach_absolute_time();
        sem_close(sem1);

        NSLog(@"0 Contention time                         = %d", stop - start);
    }

    // semaphore contention test
    {
        __block sem_t *sem1 = sem_open("sem1", O_CREAT, 0777, 0);
        __block sem_t *sem2 = sem_open("sem2", O_CREAT, 0777, 0);
        __block pthread_barrier_t startBarrier;
        pthread_barrier_init(&startBarrier, NULL, 3);
        __block pthread_barrier_t finishBarrier;
        pthread_barrier_init(&finishBarrier, NULL, 3);

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
        dispatch_async(queue, ^{
            pthread_barrier_wait(&startBarrier);
            for(int i = 0; i < 100000; i++)
            {
                sem_wait(sem1);
                sem_post(sem2);
            }
            pthread_barrier_wait(&finishBarrier);
        });
        dispatch_async(queue, ^{
            pthread_barrier_wait(&startBarrier);
            for(int i = 0; i < 100000; i++)
            {
                sem_wait(sem2);
                sem_post(sem1);
            }
            pthread_barrier_wait(&finishBarrier);
        });
        pthread_barrier_wait(&startBarrier);
        // start timing, everyone hit this point
        start = mach_absolute_time();
        // kick it off
        sem_post(sem2);
        pthread_barrier_wait(&finishBarrier);
        // stop timing, everyone hit the finish point
        stop = mach_absolute_time();
        sem_close(sem1);
        sem_close(sem2);
        NSLog(@"2 Threads always contenting time          = %d", stop - start);
        pthread_barrier_destroy(&startBarrier);
        pthread_barrier_destroy(&finishBarrier);
    }   

    // NSTask semaphore contention test
    {
        sem_t *sem1 = sem_open("sem1", O_CREAT, 0777, 0);
        sem_t *sem2 = sem_open("sem2", O_CREAT, 0777, 0);
        pthread_barrier_t startBarrier;
        pthread_barrier_init(&startBarrier, NULL, 3);
        pthread_barrier_t finishBarrier;
        pthread_barrier_init(&finishBarrier, NULL, 3);

        SemaphoreTester *tester = [[[SemaphoreTester alloc] init] autorelease];
        tester.sem1 = sem1;
        tester.sem2 = sem2;
        tester.startBarrier = &startBarrier;
        tester.finishBarrier = &finishBarrier;
        [NSThread detachNewThreadSelector:@selector(thread1) toTarget:tester withObject:nil];
        [NSThread detachNewThreadSelector:@selector(thread2) toTarget:tester withObject:nil];
        pthread_barrier_wait(&startBarrier);
        // start timing, everyone hit this point
        start = mach_absolute_time();
        // kick it off
        sem_post(sem2);
        pthread_barrier_wait(&finishBarrier);
        // stop timing, everyone hit the finish point
        stop = mach_absolute_time();
        sem_close(sem1);
        sem_close(sem2);
        NSLog(@"2 NSTasks always contenting time          = %d", stop - start);
        pthread_barrier_destroy(&startBarrier);
        pthread_barrier_destroy(&finishBarrier);
    }   

    // dispatch_semaphore non contention test
    {
        dispatch_semaphore_t sem1 = dispatch_semaphore_create(0);

        start = mach_absolute_time();
        for(int i = 0; i < 100000; i++)
        {
            dispatch_semaphore_signal(sem1);
            dispatch_semaphore_wait(sem1, DISPATCH_TIME_FOREVER);
        }
        stop = mach_absolute_time();

        NSLog(@"Dispatch 0 Contention time                = %d", stop - start);
    }


    // dispatch_semaphore non contention test
    {   
        __block dispatch_semaphore_t sem1 = dispatch_semaphore_create(0);
        __block dispatch_semaphore_t sem2 = dispatch_semaphore_create(0);
        __block pthread_barrier_t startBarrier;
        pthread_barrier_init(&startBarrier, NULL, 3);
        __block pthread_barrier_t finishBarrier;
        pthread_barrier_init(&finishBarrier, NULL, 3);

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
        dispatch_async(queue, ^{
            pthread_barrier_wait(&startBarrier);
            for(int i = 0; i < 100000; i++)
            {
                dispatch_semaphore_wait(sem1, DISPATCH_TIME_FOREVER);
                dispatch_semaphore_signal(sem2);
            }
            pthread_barrier_wait(&finishBarrier);
        });
        dispatch_async(queue, ^{
            pthread_barrier_wait(&startBarrier);
            for(int i = 0; i < 100000; i++)
            {
                dispatch_semaphore_wait(sem2, DISPATCH_TIME_FOREVER);
                dispatch_semaphore_signal(sem1);
            }
            pthread_barrier_wait(&finishBarrier);
        });
        pthread_barrier_wait(&startBarrier);
        // start timing, everyone hit this point
        start = mach_absolute_time();
        // kick it off
        dispatch_semaphore_signal(sem2);
        pthread_barrier_wait(&finishBarrier);
        // stop timing, everyone hit the finish point
        stop = mach_absolute_time();

        NSLog(@"Dispatch 2 Threads always contenting time = %d", stop - start);
        pthread_barrier_destroy(&startBarrier);
        pthread_barrier_destroy(&finishBarrier);
    }   

    // pthread_once time
    {
        pthread_once_t once = PTHREAD_ONCE_INIT;
        start = mach_absolute_time();
        for(int i = 0; i <100000; i++)
        {
            pthread_once(&once, onceFunction);
        }
        stop = mach_absolute_time();

        NSLog(@"pthread_once time  = %d", stop - start);
    }

    // dispatch_once time
    {
        dispatch_once_t once = 0;
        start = mach_absolute_time();
        for(int i = 0; i <100000; i++)
        {
            dispatch_once(&once, ^{});
        }
        stop = mach_absolute_time();

        NSLog(@"dispatch_once time = %d", stop - start);
    }

    [pool drain];
    return 0;
}

En mi iMac (Snow Leopard Server 10.6.4):

  Model Identifier: iMac7,1
  Processor Name:   Intel Core 2 Duo
  Processor Speed:  2.4 GHz
  Number Of Processors: 1
  Total Number Of Cores:    2
  L2 Cache: 4 MB
  Memory:   4 GB
  Bus Speed:    800 MHz

Puedo obtener:

0 Contention time                         =    101410439
2 Threads always contenting time          =    109748686
2 NSTasks always contenting time          =    113225207
0 Contention named semaphore time         =    166061832
2 Threads named semaphore contention time =    203913476
2 NSTasks named semaphore contention time =    204988744
Dispatch 0 Contention time                =      3411439
Dispatch 2 Threads always contenting time =    708073977
pthread_once time  =      2707770
dispatch_once time =        87433

En mi MacbookPro (Leopard 10.6.4 Snow):

  Model Identifier: MacBookPro6,2
  Processor Name:   Intel Core i5
  Processor Speed:  2.4 GHz
  Number Of Processors: 1
  Total Number Of Cores:    2 (though HT is enabled)
  L2 Cache (per core):  256 KB
  L3 Cache: 3 MB
  Memory:   8 GB
  Processor Interconnect Speed: 4.8 GT/s

Tengo:

0 Contention time                         =     74172042
2 Threads always contenting time          =     82975742
2 NSTasks always contenting time          =     82996716
0 Contention named semaphore time         =    106772641
2 Threads named semaphore contention time =    162761973
2 NSTasks named semaphore contention time =    162919844
Dispatch 0 Contention time                =      1634941
Dispatch 2 Threads always contenting time =    759753865
pthread_once time  =      1516787
dispatch_once time =       120778

en un iPhone 3GS 4.0.2 Tengo:

0 Contention time                         =      5971929
2 Threads always contenting time          =     11989710
2 NSTasks always contenting time          =     11950564
0 Contention named semaphore time         =     16721876
2 Threads named semaphore contention time =     35333045
2 NSTasks named semaphore contention time =     35296579
Dispatch 0 Contention time                =       151909
Dispatch 2 Threads always contenting time =     46946548
pthread_once time  =       193592
dispatch_once time =        25071

Preguntas y declaraciones:

  • sem_wait() y sem_post() son lentos no cuando en disputa
    • ¿Por qué es este el caso?
    • OSX no se preocupa por las API compatibles? ¿hay algún código heredado que las fuerzas que esto sea lento?
    • ¿Por qué no estos números son los mismos que las funciones dispatch_semaphore?
  • sem_wait() y sem_post() son tan lento cuando en disputa como cuando no lo son (hay una diferencia, pero pensé que sería una gran diferencia entre en disputa y no; yo esperaba números como lo fue en el código dispatch_semaphore)
  • sem_wait() y sem_post() son más lentas cuando se utilizan los semáforos con nombre.
    • ¿Por qué? Se esto porque el semáforo ha de ser sincronizado entre los procesos? tal vez hay más equipaje al hacer eso.
  • dispatch_semaphore_wait() y dispatch_semaphore_signal() están locos rápido cuando no está bajo la contención (sin sorpresa aquí desde que Apple está promocionando esto mucho).
  • dispatch_semaphore_wait() y dispatch_semaphore_signal() son 3x más lento que sem_wait() y sem_post() cuando en disputa
    • ¿Por qué es tan lento? Esto no tiene sentido para mi. Yo habría esperado que esto sea a la par con la sem_t en disputa.
  • dispatch_once() es más rápido que pthread_once(), alrededor de 10 veces, ¿por qué? La única cosa que puedo decir de las cabeceras es que no hay carga llamada a función con dispatch_once() que con pthread_once().

de motivación: Se me presenta con 2 juegos de herramientas para hacer el trabajo para los semáforos o una vez llamadas (que en realidad encontramos otro semáforo variantes en el ínterin, pero voy a ignorar los menos educados como una mejor opción). Sólo quiero saber cuál es la mejor herramienta para el trabajo (Si usted tiene la opción para atornillar un tornillo con una cabeza plana Philips o, elegiría Philips si yo no tengo que apretar el tornillo de cabeza plana y si tengo que apriete el tornillo). Parece que si empiezo a escribir con utilidades libdispatch Puede que no sea capaz de ellas puerto para otros sistemas operativos que no tienen libdispatch trabajando todavía ... pero es tan atractiva para su uso;)

En su forma actual: Me va a utilizar libdispatch cuando no tiene que preocuparse acerca de la portabilidad y POSIX llamadas cuando lo haga.

Gracias!

¿Fue útil?

Solución

sem_wait () y sem_post () son instalaciones de sincronización peso pesados ??que pueden ser utilizados entre los procesos. Ellos siempre implican ida y vuelta al núcleo, y probablemente siempre exigen un hilo para ser reprogramado. Por lo general, no son la mejor opción para la sincronización en proceso. No estoy seguro de por qué las variantes nombradas serían más lentos que los anónimos ...

Mac OS X es realmente muy bueno acerca de la compatibilidad POSIX ... Pero las especificaciones POSIX tener una gran cantidad de funciones opcionales, y el Mac no tener a todos ellos. Su mensaje es en realidad el primero que he escuchado de pthread_barriers, así que supongo que son ya sea relativamente reciente, o no tan común. (No he prestado mucha atención a la evolución pthreads durante los últimos años más o menos diez.)

La razón por la materia de despacho se desmorona bajo extrema contención forzada es probablemente debido a que bajo las sábanas el comportamiento es similar a bloqueos de giro. Sus hilos de trabajo de despacho están muy probable desperdiciando una buena parte de sus cuantos bajo el supuesto optimista de que el recurso en disputa va a estar disponible cualquier ciclo de ahora ... un poco de tiempo con el tiburón le diría a ciencia cierta. El punto para llevar a casa, sin embargo, debe ser que "optimizando" la paliza durante la contención es una mala inversión de tiempo programador. En lugar de pasar el tiempo optimizando el código de evitar contención pesada en el primer lugar.

Si realmente tiene un recurso que es un cuello de botella no-evitable dentro de su proceso, poner un semáforo alrededor de ella es enormemente inferior al óptimo. Póngalo en su propia cola de distribución en serie, y tanto como posibles bloques dispatch_async a ejecutar en esa cola.

Por último, dispatch_once () es más rápido que pthread_once () porque está spec'da e implementado para ser rápido en los procesadores actuales. Es probable que Apple podría acelerar la aplicación pthread_once (), como sospecho que la implementación de referencia utiliza primitivas de sincronización pthread, pero ... bueno ... han proporcionado toda la bondad libdispatch lugar. : -)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top