13

Tôi muốn biết điều gì sẽ tốt hơn/nhanh hơn khi sử dụng các cuộc gọi POSIX như pthread_once()sem_wait() hoặc các hàm dispatch_ *, vì vậy tôi đã tạo một thử nghiệm nhỏ và ngạc nhiên trước kết quả (câu hỏi và kết quả là ở cuối).Thử nghiệm hiệu suất: sem_t v.s. dispatch_semaphore_t và pthread_once_t v.s. dispatch_once_t

Trong mã thử nghiệm, tôi đang sử dụng mach_absolute_time() để gọi thời gian. Tôi thực sự không quan tâm rằng điều này không khớp chính xác với nano giây; Tôi so sánh các giá trị với nhau để các đơn vị thời gian chính xác không quan trọng, chỉ có sự khác biệt giữa khoảng thời gian thực hiện. Các số trong phần kết quả có thể lặp lại và không được tính trung bình; Tôi có thể lấy trung bình thời gian nhưng tôi không tìm kiếm con số chính xác.

test.m (giao diện điều khiển ứng dụng đơn giản, dễ để biên dịch):

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

On iMac của tôi (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 

tôi nhận được:

 
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 

Trên MacbookPro của tôi (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 

tôi nhận:

 
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 

trên iPhone 3GS 4.0.2 tôi nhận:

 

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 

câu hỏi và phát biểu:

  • sem_wait()sem_post() là chậm khi không theo tranh chấp
    • tại sao lại xảy ra trường hợp này?
    • OSX có quan tâm đến các API tương thích không? có một số mã di sản buộc phải làm chậm không?
    • Tại sao những con số này không giống với các hàm dispatch_semaphore?
  • sem_wait()sem_post() chỉ là chậm khi dưới tranh như khi họ không (có sự khác nhau nhưng tôi nghĩ rằng nó sẽ là một sự khác biệt rất lớn giữa dưới tranh và không; tôi mong đợi con số giống như những gì đang ở trong dispatch_semaphore code)
  • sem_wait()sem_post() chậm hơn khi sử dụng các ẩn dụ có tên.
    • Tại sao? này là bởi vì semaphore phải được đồng bộ giữa các quá trình? có thể có nhiều hành lý hơn khi làm điều đó.
  • dispatch_semaphore_wait()dispatch_semaphore_signal() rất nhanh khi không có tranh chấp (không có gì ngạc nhiên vì táo đang quảng cáo rất nhiều).
  • dispatch_semaphore_wait()dispatch_semaphore_signal() được 3x chậm hơn so với sem_wait()sem_post() khi dưới tranh
    • Tại sao điều này quá chậm? điều này không có ý nghĩa với tôi. Tôi đã có thể mong đợi điều này là ngang bằng với sem_t dưới ganh đua.
  • dispatch_once() nhanh hơn pthread_once(), khoảng 10x, tại sao? Điều duy nhất tôi có thể nói từ các tiêu đề là không có gánh nặng cuộc gọi chức năng với dispatch_once() hơn với pthread_once().

Động lực: Tôi đã trình bày với 2 bộ công cụ để hoàn thành công việc cho Cột hoặc một lần gọi (Tôi thực sự tìm thấy biến thể semaphore khác trong khi chờ đợi, nhưng tôi sẽ bỏ qua những trừ khi lớn lên như một lựa chọn tốt hơn). Tôi chỉ muốn biết công cụ tốt nhất cho công việc là gì (Nếu bạn có tùy chọn vặn ốc vít bằng một chiếc philips hoặc flathead, tôi sẽ chọn philips nếu tôi không phải vặn ốc vít và flathead nếu tôi phải mô-men xoắn ốc vít). Dường như nếu tôi bắt đầu viết các tiện ích với libdispatch tôi có thể không thể chuyển chúng sang các hệ điều hành khác không có libdispatch đang hoạt động ... nhưng nó quá hấp dẫn để sử dụng;)

Vì nó là viết tắt: Tôi sẽ sử dụng libdispatch khi tôi không phải lo lắng về tính di động và cuộc gọi POSIX khi tôi thực hiện.

Cảm ơn!

Trả lời

10

sem_wait() và sem_post() là các cơ sở đồng bộ hóa trọng lượng nặng có thể được sử dụng giữa các quy trình. Họ luôn luôn liên quan đến các chuyến đi vòng đến hạt nhân, và có lẽ luôn luôn yêu cầu của bạn để được lên lịch lại. Họ thường không phải là sự lựa chọn đúng cho đồng bộ hóa trong quá trình. Tôi không chắc tại sao các biến thể được đặt tên sẽ chậm hơn các biến thể ẩn danh ...

Mac OS X thực sự khá tốt về tính tương thích Posix ... Nhưng thông số Posix có nhiều chức năng tùy chọn và máy Mac không có tất cả. Bài đăng của bạn thực sự là lần đầu tiên tôi nghe nói về pthread_barriers, vì vậy tôi đoán chúng tương đối gần đây, hoặc không phải là tất cả những gì phổ biến. (Tôi đã không chú ý nhiều đến sự phát triển của pthreads trong mười năm qua.)

Lý do các công cụ gửi đi bị ép buộc dưới ganh đua cực đoan có thể là do nằm trong phạm vi hành vi tương tự như ổ quay. Chủ đề công nhân điều phối của bạn rất có khả năng lãng phí một lượng lớn lượng tử của họ theo giả định lạc quan rằng tài nguyên dưới ganh đua sẽ có chu kỳ hiện tại ... Một chút thời gian với Shark sẽ bảo bạn chắc chắn. Tuy nhiên, điểm lấy về nhà nên là "tối ưu hóa" sự rung chuyển trong cuộc tranh cãi là đầu tư thời gian lập trình kém. Thay vào đó, hãy dành thời gian tối ưu hóa mã để tránh số tranh chấp nặng ngay từ đầu.

Nếu bạn thực sự có tài nguyên là nút cổ chai không thể tránh khỏi trong quá trình của bạn, việc đặt một semaphore xung quanh nó là cực kỳ tối ưu. Đặt nó vào hàng đợi công văn nối tiếp của riêng nó và càng nhiều khối dispatch_async có thể được thực hiện trên hàng đợi đó.

Cuối cùng, dispatch_once() nhanh hơn pthread_once() vì nó được xác định và triển khai nhanh trên các bộ vi xử lý hiện tại. Có lẽ Apple có thể tăng tốc độ thực thi pthread_once(), vì tôi nghi ngờ việc triển khai tham chiếu sử dụng các nguyên tắc đồng bộ hóa pthread, nhưng ... ừm ... chúng đã cung cấp tất cả sự tốt đẹp libdispatch thay thế.:-)

+2

Điểm tốt về sem_wait/post vì dispatch_semaphores không phải xử lý việc chuyển ngữ cảnh sang hạt nhân (có vẻ như là một duh! Now;). Tôi chịu trách nhiệm bổ sung tính tương thích POSIX cho một hạt nhân được phát triển tại nhà cho các hệ thống nhúng và đã tìm thấy các rào cản hữu ích cho việc tạo ra các xét nghiệm đơn vị. Tôi đã không cố gắng tối ưu hóa một tình huống cụ thể hơn một tình huống khác, nhưng cố gắng tìm ra 2 công cụ mà tôi đã đưa ra, đó là công cụ tốt nhất cho công việc (nếu tôi có tùy chọn vặn vít bằng một con lăn hoặc một cái vuốt. .. Tôi sẽ sử dụng philips). Cập nhật câu hỏi với động lực ... –

+0

pthread_once() có thể/nên được thực hiện với nguyên tử cho kiểm tra "được gọi"; khi chờ đợi cho cuộc gọi để hoàn thành có Tôi đồng ý nó sẽ được sử dụng một pthread_mutex để chặn các chủ đề khác (đó là cách tôi đã làm nó). Tuy nhiên, trong trường hợp thử nghiệm này, không có sự ngăn chặn và không cần thiết phải chuyển đổi ngữ cảnh hạt nhân. Với điều này nói rằng tôi vẫn không hiểu tại sao có một sự khác biệt 10x. Tôi đoán libdispatch được tối ưu hóa nhiều hơn so với các cuộc gọi posix. –

+0

quan sát khác: ẩn dụ ẩn dụ nên hoạt động như dispatch_semaphores kể từ khi bạn không thể lấy chúng từ một quá trình khác. Bạn chỉ nên chuyển ngữ cảnh sang nhân khi bạn thực sự phải chặn hoặc đánh thức các chủ đề bị chặn (cho rằng táo đang sử dụng nguyên tử cho semaphores, đó là những gì tôi đã làm cho các semaphores trong hệ điều hành của chúng tôi). –

Các vấn đề liên quan