13

Volevo sapere cosa sarebbe stato meglio/più veloce usare le chiamate POSIX come pthread_once() e sem_wait() o le funzioni dispatch_ *, così ho creato un piccolo test e sono sorpreso dei risultati (domande e risultati sono alla fine).Test delle prestazioni: sem_t v.s. dispatch_semaphore_t e pthread_once_t v.s. dispatch_once_t

Nel codice di prova sto utilizzando mach_absolute_time() per l'ora delle chiamate. Non mi interessa davvero che questo non corrisponda esattamente ai nano-secondi; Sto confrontando i valori l'uno con l'altro in modo che le unità di tempo esatte non contengano, solo le differenze tra l'intervallo lo fanno. I numeri nella sezione dei risultati sono ripetibili e non mediati; Avrei potuto calcolare la media dei tempi ma non sto cercando 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 

ottengo:

 
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 contenzioso
    • perché è questo il caso?
    • a OSX non interessa le API compatibili? c'è qualche codice legacy che costringe questo a essere lento?
    • Perché questi numeri non sono uguali alle 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ù lenti quando si utilizzano i nomi dei semafori.
    • Perché? è questo perché il semaforo deve essere sincronizzato tra i processi? forse c'è più bagaglio quando lo fai.
  • dispatch_semaphore_wait() e dispatch_semaphore_signal() sono pazzi veloce 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 che questo fosse alla pari con il sem_t in discussione.
  • dispatch_once() è più veloce di pthread_once(), circa 10 volte, perché? L'unica cosa che posso dire dalle intestazioni è che non vi è alcun onere di chiamata di funzione con dispatch_once() rispetto a 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 altre varianti semaforo nel frattempo, ma mi ignorare quelli meno allevato come un migliore opzione). Voglio solo sapere qual è lo strumento migliore per il lavoro (Se hai la possibilità di avvitare una vite con un philips o un testapiatta, sceglierei i philips se non devo stringere la vite e la lama piatta se devo serrare la vite). Sembra che se inizio a scrivere programmi con libdispatch potrei non essere in grado di portarli su altri sistemi operativi che non hanno ancora libdispatch funzionante ... ma è così allettante da usare;)

Così com'è: Utilizzerò libdispatch quando non devo preoccuparmi della portabilità e delle chiamate POSIX quando lo faccio.

Grazie!

risposta

10

sem_wait() e sem_post() sono funzioni di sincronizzazione di peso elevato che possono essere utilizzate tra i processi. Coinvolgono sempre round trip nel kernel e probabilmente richiedono sempre la riprogrammazione del thread. In genere non sono la scelta giusta per la sincronizzazione in-process. Non sono sicuro del motivo per cui le varianti nominate sarebbero più lente di quelle anonime ...

Mac OS X è in realtà abbastanza buono per la compatibilità con Posix ... Ma le specifiche Posix hanno un sacco di funzioni opzionali, e il Mac non li ha tutti Il tuo post è in realtà il primo che abbia mai sentito parlare di pthread_barriers, quindi suppongo che siano relativamente recenti, o non tutti così comuni. (Non ho prestato molta attenzione all'evoluzione di pthreads negli ultimi dieci anni circa).

Il motivo per cui la roba di dispatch cade a pezzi sotto la contesa forzata estrema è probabilmente perché sotto le coperte il comportamento è simile a spin lock. I tuoi thread di lavoro di spedizione sono molto probabilmente sprecando una buona fetta dei loro sotto l'ipotesi ottimistica che la risorsa in conflitto sarà disponibile in qualsiasi momento ... Un po 'di tempo con Shark te lo direbbe sicuramente. Il punto da portare a casa, però, dovrebbe essere che "ottimizzare" il thrashing durante la contesa è uno scarso investimento del tempo del programmatore. Passa invece il tempo a ottimizzare il codice per evita la contesa pesante in primo luogo.

Se si dispone davvero di una risorsa che è un collo di bottiglia non evitabile all'interno del processo, inserire un semaforo attorno a esso è estremamente sub-ottimale. Mettilo sulla propria coda di invio seriale e il più possibile i blocchi dispatch_async da eseguire su quella coda.

Infine, dispatch_once() è più veloce di pthread_once() perché è spec'd e implementato per essere veloce sui processori attuali. Probabilmente Apple potrebbe accelerare l'implementazione di pthread_once(), poiché sospetto che l'implementazione di riferimento usi le primitive di sincronizzazione pthread, ma ... beh ... hanno fornito tutta la bontà di libdispatch.:-)

+2

Buon punto sul sem_wait/post poiché dispatch_semaphores non ha a che fare con lo switch di contesto sul kernel (sembra un duh! Now;). Ero responsabile di aggiungere la compatibilità POSIX a un kernel interno per i sistemi embedded e ho trovato barriere utili per la creazione di test unitari. Non stavo cercando di ottimizzare una situazione particolare piuttosto che un'altra, ma piuttosto provo a capire i 2 strumenti che mi danno, che è lo strumento migliore per il lavoro (se ho la possibilità di avvitare una vite con un philips o una lama piatta. .. Userò philips). Aggiornamento domanda con motivazione ... –

+0

pthread_once() può/deve essere implementato con atomics per il controllo "chiamato"; quando aspetto che la chiamata si completi, acconsento che usi pthread_mutex per bloccare altri thread (è così che l'ho fatto). In questo caso di test, tuttavia, non vi è alcun blocco e nessuna necessità di un cambio di contesto del kernel. Detto questo, continuo a non capire perché c'è una differenza di 10 volte. Immagino che libdispatch sia ottimizzato più delle chiamate posix. –

+0

un'altra osservazione: i semafori anonimi dovrebbero funzionare come il dispatch_semaphores poiché non puoi recuperarli da un altro processo. Dovresti solo cambiare contesto al kernel quando devi effettivamente bloccare o svegliare i thread bloccati (dato che Apple sta usando l'atomica per i semafori, che è quello che ho fatto per i nostri semafori nel nostro sistema operativo). –