Тест производительности:sem_t против.send_semaphore_t и pthread_once_t против.диспетчеризация_once_t

StackOverflow https://stackoverflow.com/questions/3640853

Вопрос

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

В тестовом коде я использую 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 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?есть ли какой-то устаревший код, который заставляет это работать медленно?
    • Почему эти числа не совпадают с числами функций send_semaphore?
  • sem_wait() и sem_post() работают так же медленно, когда есть конкуренция, как и когда ее нет (разница есть, но я думал, что это будет огромная разница между тем, когда есть конкуренция, и нет;Я ожидал чисел, подобных тем, что были в коде send_semaphore)
  • sem_wait() и sem_post() работают медленнее при использовании именованных семафоров.
    • Почему?это потому, что семафор должен синхронизироваться между процессами?возможно, при этом возникает больше багажа.
  • 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().

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

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

Спасибо!

Это было полезно?

Решение

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top