Тест производительности: sem_t против dispatch_semaphore_t и pthread_once_t против dispatch_once_t - PullRequest
14 голосов
/ 04 сентября 2010

Я хотел знать, что было бы лучше / быстрее использовать вызовы POSIX, такие как pthread_once() и sem_wait() или функции dispatch_ *, поэтому я создал небольшой тест и удивлен результатами (вопросы и результаты находятся наконец).

В тестовом коде я использую mach_absolute_time () для определения времени вызовов.Мне действительно все равно, что это не совсем соответствует нано-секундам;Я сравниваю значения друг с другом, поэтому точные единицы времени не имеют значения, имеют значение только различия между интервалами.Числа в разделе результатов являются повторяемыми и не усредняются;Я мог бы усреднить время, но я не ищу точных чисел.

test.m (простое консольное приложение; легко компилировать):

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

На моем 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

Я получаю:

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

На моем 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

Я получил:

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

на iPhone 3GS 4.0.2 Я получил:


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

Вопросы и заявления:

  • sem_wait() иsem_post() медленные, когда не в споре
    • почему это так?
    • разве OSX не заботится о совместимых API?Есть какой-то устаревший код, который заставляет это быть медленным?
    • Почему эти числа не совпадают с функциями dispatch_semaphore?
  • sem_wait() и sem_post() столь же медленны, когда находятся в состоянии раздора, чем когда их нет(есть разница, но я подумал, что это будет огромная разница между недоразумением и нет; я ожидал, что числа, подобные тому, что было в коде dispatch_semaphore)
  • sem_wait() и sem_post() медленнее при использовании namedсемафоры.
    • Почему?это потому, что семафор должен синхронизироваться между процессами?возможно, в этом есть больше багажа.
  • dispatch_semaphore_wait() и dispatch_semaphore_signal() сумасшедшие быстро, когда не спорят (здесь нет ничего удивительного, так как Apple много об этом говорит).
  • dispatch_semaphore_wait() и dispatch_semaphore_signal() в 3 раза медленнее, чем sem_wait() и sem_post(), когда находятся в состоянии конфликта
    • Почему это так медленно?это не имеет смысла для меня.Я ожидал бы, что это будет наравне с оспариваемым sem_t.
  • dispatch_once() быстрее, чем pthread_once(), примерно в 10 раз, почему?Единственное, что я могу сказать по заголовкам, это то, что нет никакой нагрузки на вызовы функций с dispatch_once(), чем с pthread_once().

Мотивация: У меня есть 2 набораинструментов для выполнения работы с семафорами или однократными вызовами (тем временем я нашел другие варианты семафоров, но я их проигнорирую, если не предложу лучший вариант).Я просто хочу знать, какой инструмент лучше всего подходит для этой работы (если у вас есть возможность ввинчивать винт с помощью крестообразной или плоской головки, я бы выбрал филипс, если мне не нужно затягивать винт и плоскую головку, если мне нужнозатяните винт).Похоже, что если я начну писать утилиты с помощью libdispatch, я не смогу перенести их на другие операционные системы, у которых пока не работает libdispatch ... но это так заманчиво для использования;)

Как естьЯ буду использовать libdispatch, когда мне не придется беспокоиться о переносимости и вызовах POSIX.

Спасибо!

1 Ответ

11 голосов
/ 05 сентября 2010

sem_wait () и sem_post () являются мощными средствами синхронизации, которые могут использоваться между процессами.Они всегда включают в себя обращения к ядру и, вероятно, всегда требуют переназначения вашего потока.Как правило, они не являются правильным выбором для синхронизации в процессе.Я не уверен, почему названные варианты будут медленнее, чем анонимные ...

Mac OS X на самом деле очень хороша в совместимости с Posix ... Но в спецификациях Posix есть много дополнительных функций, иMac не имеет их всех.На самом деле ваш пост является первым, что я когда-либо слышал о pthread_barriers, так что я предполагаю, что они либо относительно недавние, либо не настолько распространенные.(Я не обращал особого внимания на эволюцию потоков за последние десять лет или около того.)

Причина, по которой вещи отгрузки распадаются при вынужденном экстремальном конфликте, вероятно, заключается в том, что под прикрытием поведение похоже на спин-блокировки,Скорее всего, ваши рабочие потоки диспетчеризации тратят большую часть своих квантов при оптимистическом предположении, что конфликтующий ресурс будет доступен в любом цикле сейчас ... Немного времени с Акулой скажу вам наверняка.Главная точка зрения, однако, должна состоять в том, что «оптимизация» обмолота во время раздора - это плохая трата времени программиста.Вместо этого потратьте время на оптимизацию кода, чтобы, в первую очередь, избежать серьезных конфликтов.

Если у вас действительно есть ресурс, который является узким местом, которого невозможно избежать, в вашем процессе, поместите вокруг него семафорявляется массивно неоптимальным.Поместите его в свою собственную очередь последовательной отправки и как можно больше блоков dispatch_async, которые будут выполняться в этой очереди.

Наконец, dispatch_once () быстрее, чем pthread_once (), потому что он задан и реализован, чтобы быть быстрымна текущих процессорах.Вероятно, Apple могла бы ускорить реализацию pthread_once (), так как я подозреваю, что эталонная реализация использует примитивы синхронизации pthread, но ... хорошо ... они вместо этого предоставили все преимущества libdispatch.: -)

...