Note that there are some explanatory texts on larger screens.

plurals
  1. POPerformance test: sem_t v.s. dispatch_semaphore_t and pthread_once_t v.s. dispatch_once_t
    primarykey
    data
    text
    <p>I wanted to know what would be better/faster to use POSIX calls like <code>pthread_once()</code> and <code>sem_wait()</code> or the dispatch_* functions, so I created a little test and am surprised at the results (questions and results are at the end). </p> <p>In the test code I am using mach_absolute_time() to time the calls. I really don’t care that this is not exactly matching up with nano-seconds; I am comparing the values with each other so the exact time units don't matter, only the differences between the interval do. The numbers in the results section are repeatable and not averaged; I could have averaged the times but I am not looking for exact numbers.</p> <p>test.m (simple console application; easy to compile):</p> <pre><code>#import &lt;Foundation/Foundation.h&gt; #import &lt;dispatch/dispatch.h&gt; #include &lt;semaphore.h&gt; #include &lt;pthread.h&gt; #include &lt;time.h&gt; #include &lt;mach/mach_time.h&gt; // *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(&amp;barrier-&gt;mutex, 0) &lt; 0) { return -1; } if(pthread_cond_init(&amp;barrier-&gt;cond, 0) &lt; 0) { pthread_mutex_destroy(&amp;barrier-&gt;mutex); return -1; } barrier-&gt;tripCount = count; barrier-&gt;count = 0; return 0; } int pthread_barrier_destroy(pthread_barrier_t *barrier) { pthread_cond_destroy(&amp;barrier-&gt;cond); pthread_mutex_destroy(&amp;barrier-&gt;mutex); return 0; } int pthread_barrier_wait(pthread_barrier_t *barrier) { pthread_mutex_lock(&amp;barrier-&gt;mutex); ++(barrier-&gt;count); if(barrier-&gt;count &gt;= barrier-&gt;tripCount) { barrier-&gt;count = 0; pthread_cond_broadcast(&amp;barrier-&gt;cond); pthread_mutex_unlock(&amp;barrier-&gt;mutex); return 1; } else { pthread_cond_wait(&amp;barrier-&gt;cond, &amp;(barrier-&gt;mutex)); pthread_mutex_unlock(&amp;barrier-&gt;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 &lt; 100000; i++) { sem_wait(sem1); sem_post(sem2); } pthread_barrier_wait(finishBarrier); } - (void)thread2 { pthread_barrier_wait(startBarrier); for(int i = 0; i &lt; 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 &lt; 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(&amp;startBarrier, NULL, 3); __block pthread_barrier_t finishBarrier; pthread_barrier_init(&amp;finishBarrier, NULL, 3); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); dispatch_async(queue, ^{ pthread_barrier_wait(&amp;startBarrier); for(int i = 0; i &lt; 100000; i++) { sem_wait(sem1); sem_post(sem2); } pthread_barrier_wait(&amp;finishBarrier); }); dispatch_async(queue, ^{ pthread_barrier_wait(&amp;startBarrier); for(int i = 0; i &lt; 100000; i++) { sem_wait(sem2); sem_post(sem1); } pthread_barrier_wait(&amp;finishBarrier); }); pthread_barrier_wait(&amp;startBarrier); // start timing, everyone hit this point start = mach_absolute_time(); // kick it off sem_post(sem2); pthread_barrier_wait(&amp;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(&amp;startBarrier); pthread_barrier_destroy(&amp;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(&amp;startBarrier, NULL, 3); pthread_barrier_t finishBarrier; pthread_barrier_init(&amp;finishBarrier, NULL, 3); SemaphoreTester *tester = [[[SemaphoreTester alloc] init] autorelease]; tester.sem1 = sem1; tester.sem2 = sem2; tester.startBarrier = &amp;startBarrier; tester.finishBarrier = &amp;finishBarrier; [NSThread detachNewThreadSelector:@selector(thread1) toTarget:tester withObject:nil]; [NSThread detachNewThreadSelector:@selector(thread2) toTarget:tester withObject:nil]; pthread_barrier_wait(&amp;startBarrier); // start timing, everyone hit this point start = mach_absolute_time(); // kick it off sem_post(sem2); pthread_barrier_wait(&amp;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(&amp;startBarrier); pthread_barrier_destroy(&amp;finishBarrier); } // dispatch_semaphore non contention test { dispatch_semaphore_t sem1 = dispatch_semaphore_create(0); start = mach_absolute_time(); for(int i = 0; i &lt; 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(&amp;startBarrier, NULL, 3); __block pthread_barrier_t finishBarrier; pthread_barrier_init(&amp;finishBarrier, NULL, 3); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); dispatch_async(queue, ^{ pthread_barrier_wait(&amp;startBarrier); for(int i = 0; i &lt; 100000; i++) { dispatch_semaphore_wait(sem1, DISPATCH_TIME_FOREVER); dispatch_semaphore_signal(sem2); } pthread_barrier_wait(&amp;finishBarrier); }); dispatch_async(queue, ^{ pthread_barrier_wait(&amp;startBarrier); for(int i = 0; i &lt; 100000; i++) { dispatch_semaphore_wait(sem2, DISPATCH_TIME_FOREVER); dispatch_semaphore_signal(sem1); } pthread_barrier_wait(&amp;finishBarrier); }); pthread_barrier_wait(&amp;startBarrier); // start timing, everyone hit this point start = mach_absolute_time(); // kick it off dispatch_semaphore_signal(sem2); pthread_barrier_wait(&amp;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(&amp;startBarrier); pthread_barrier_destroy(&amp;finishBarrier); } // pthread_once time { pthread_once_t once = PTHREAD_ONCE_INIT; start = mach_absolute_time(); for(int i = 0; i &lt;100000; i++) { pthread_once(&amp;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 &lt;100000; i++) { dispatch_once(&amp;once, ^{}); } stop = mach_absolute_time(); NSLog(@"dispatch_once time = %d", stop - start); } [pool drain]; return 0; } </code></pre> <p>On My iMac (Snow Leopard Server 10.6.4):</p> <pre> 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 </pre> <p>I get:</p> <pre> 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 </pre> <p>On my MacbookPro (Snow Leopard 10.6.4):</p> <pre> 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 </pre> <p>I got:</p> <pre> 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 </pre> <p>on an iPhone 3GS 4.0.2 I got:</p> <pre> 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 </pre> <p><strong>Questions and statements:</strong></p> <ul> <li><code>sem_wait()</code> and <code>sem_post()</code> are slow when not under contention <ul> <li>why is this the case? </li> <li>does OSX not care about compatible APIs? is there some legacy code that forces this to be slow? </li> <li>Why aren't these numbers the same as the dispatch_semaphore functions?</li> </ul></li> <li><code>sem_wait()</code> and <code>sem_post()</code> are just as slow when under contention as when they are not (there is a difference but I thought that it would be a huge difference between under contention and not; I expected numbers like what was in the dispatch_semaphore code)</li> <li><code>sem_wait()</code> and <code>sem_post()</code> are slower when using named semaphores. <ul> <li>Why? is this because the semaphore has to be synced between processes? maybe there is more baggage when doing that.</li> </ul></li> <li><code>dispatch_semaphore_wait()</code> and <code>dispatch_semaphore_signal()</code> are crazy fast when not under contention (no surprise here since apple is touting this a lot).</li> <li><code>dispatch_semaphore_wait()</code> and <code>dispatch_semaphore_signal()</code> are 3x slower than <code>sem_wait()</code> and <code>sem_post()</code> when under contention <ul> <li>Why is this so slow? this does not make sense to me. I would have expected this to be on par with the sem_t under contention.</li> </ul></li> <li><code>dispatch_once()</code> is faster than <code>pthread_once()</code>, around 10x, why? The only thing I can tell from the headers is that there is no function call burden with <code>dispatch_once()</code> than with <code>pthread_once()</code>.</li> </ul> <p><strong>Motivation:</strong> I am presented with 2 sets of tools to get the job done for semaphores or once calls (I actually found other semaphore variants in the meantime, but I will ignore those unless brought up as a better option). I just want to know what is the best tool for the job (If you have the option for screwing in a screw with a philips or flathead, I would choose philips if I don't have to torque the screw and flathead if I have to torque the screw). It seems that if I start writing utilities with libdispatch I might not be able to port them to other operating systems that do not have libdispatch working yet... but it is so enticing to use ;)</p> <p>As it stands: I will be using libdispatch when I don't have to worry about portability and POSIX calls when I do. </p> <p>Thanks!</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload