Domanda

volevo sapere cosa sarebbe meglio / più veloce per utilizzare le chiamate POSIX come pthread_once() e sem_wait() o dispatch_ * funzioni, così ho creato un piccolo test e sono sorpreso dei risultati (domande ed i risultati sono alla fine).

Nel codice di prova che sto usando mach_absolute_time () per cronometrare le chiamate. Io davvero non importa che questo non è esattamente la corrispondenza con nano-secondi; Sto confrontando i valori con l'altro in modo che le unità di tempo esatto non importa, solo le differenze tra l'intervallo fanno. I numeri nella sezione dei risultati sono ripetibili e non mediato; Avrei potuto media i tempi, ma io non sono alla ricerca di numeri esatti.

test.m (semplice applicazione console, facile da compilare):

#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;
}

sul mio 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

I ottenere:

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

Sul mio MacbookPro (Snow Leopard 10.6.4):

  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

ho ottenuto:

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

su un iPhone 3GS 4.0.2 ho ottenuto:

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

Domande e dichiarazioni:

  • sem_wait() e sem_post() sono lenti quando non è sotto contesa
    • perché è questo il caso?
    • non OSX si preoccupa di API compatibili? c'è qualche codice legacy che le forze di questo per essere lento?
    • Perché non questi numeri sono gli stessi delle funzioni dispatch_semaphore?
  • sem_wait() e sem_post() sono altrettanto lento quando sotto contesa, come quando non sono (c'è una differenza, ma ho pensato che sarebbe una grande differenza tra sotto contesa e non; mi aspettavo numeri come quello che era nel codice dispatch_semaphore)
  • sem_wait() e sem_post() sono più lente quando si utilizza semafori nome.
    • Perché? è questo perché il semaforo è di essere sincronizzati tra i processi? forse non v'è più il bagaglio quando farlo.
  • dispatch_semaphore_wait() e dispatch_semaphore_signal() sono veloci pazzo quando non è sotto contesa (nessuna sorpresa qui da quando Apple sta sollecitando questo molto).
  • dispatch_semaphore_wait() e dispatch_semaphore_signal() sono 3x più lento di sem_wait() e sem_post() quando sotto contesa
    • Perché è così lento? Questo non ha senso per me. Mi sarei aspettato questo per essere alla pari con l'sem_t sotto contesa.
  • dispatch_once() è più veloce di pthread_once(), intorno 10x, perché? L'unica cosa che posso dire dalle intestazioni è che non v'è alcun onere funzione chiamata con dispatch_once() che con pthread_once().

Motivazione: Mi sono presentato con 2 set di strumenti per ottenere il lavoro fatto per i semafori o una volta chiamate (in realtà ho trovato altro semaforo varianti nel frattempo, ma mi ignorare quelli meno allevato come una scelta migliore). Voglio solo sapere qual è il miglior strumento per il lavoro (se si ha la possibilità per avvitare una vite con una Philips o piatta, avrei scelto Philips se non ho a serrare la vite a testa piatta e se devo serrare la vite). Sembra che se mi metto a scrivere utility con libdispatch potrei non essere in grado di porto ai diversi sistemi operativi che non hanno libdispatch lavorare ancora ... ma è così allettante da usare;)

Così com'è: Userò libdispatch quando non devono preoccuparsi di portabilità e POSIX chiamate quando lo faccio.

Grazie!

È stato utile?

Soluzione

sem_wait () e sem_post () sono pesanti strutture sincronizzazione peso che possono essere utilizzati tra processi. Essi comportano sempre andata e ritorno al kernel, e probabilmente sempre richiedono il tuo thread essere riprogrammata. Essi non sono generalmente la scelta giusta per la sincronizzazione in-process. Io non sono sicuro perché le varianti di nome sarebbe più lento di quelli anonimi ...

Mac OS X è in realtà piuttosto bene sulla compatibilità Posix ... Ma le specifiche Posix hanno un sacco di funzioni opzionali, e il Mac non li hanno tutti. Il tuo post è in realtà la prima che abbia mai sentito di pthread_barriers, quindi sto cercando di indovinare sono o relativamente recente, o meno tutto ciò che comune. (Non ho prestato molta attenzione a pthreads di evoluzione per i dieci anni passati o giù di lì).

La ragione la roba spedizione cade a pezzi sotto forzata estrema contesa è probabilmente perché sotto le coperte il comportamento è simile alle serrature di spin. I suoi spedizione thread di lavoro sono molto probabilmente sprecando una buona fetta di loro quanti sotto l'ipotesi ottimista sul fatto che la risorsa sotto contesa sta per essere disponibile in qualsiasi ciclo di ora ... un po 'di tempo con Shark sarebbe dire di sicuro. Il punto da portare a casa, però, dovrebbe essere che "ottimizzare" la botte durante contesa è un cattivo investimento di tempo programmatore. Invece trascorrere il tempo ottimizzare il codice per Evitare pesante contesa, in primo luogo.

Se hai davvero una risorsa che è un collo di bottiglia non-evitabili all'interno del vostro processo, mettendo un semaforo intorno ad esso è massicciamente sub-ottimale. Metterlo sulla propria coda di invio di serie e il più possibile i blocchi dispatch_async da eseguire su quella coda.

Infine, dispatch_once () è più veloce di pthread_once () perché è spec e implementato per essere veloce su processori attuali. Probabilmente Apple potrebbe accelerare l'attuazione pthread_once (), come sospetto l'implementazione di riferimento utilizza primitive di sincronizzazione pthread, ma ... beh ... che hanno fornito tutte della bontà libdispatch invece. : -)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top