четверг, 20 ноября 2008 г.

О скорости блокировки с помощью CRITICAL_SECTION

Задумал тут провести сравнения скорости блокировки потоков с помощью CRITICAL_SECTION и с помощью других подходов (CAS, spinlock).
Результат в общем-то получился предсказуемым - с помощью CAS быстрее...В среднем раза в 2. Хотя...не всегда. Периодически тестовые запуски показывали что код с использованием CRITICAL_SECTION так же быстр или даже чуть быстрее...
Я списал эти "неправильные" результаты на флюктуации и решил сравнить скорость блокировки потоков с помощью CRITICAL_SECTION и с помощью spinlock.
Код для реализации spinlock я взял отсюда и немного причесал. В принципе он совпадает с кодом приведенным в википедии.
(также я пробывал использовать код с codeguru но там результаты еще хуже)

Выяснилось...выяснилось что критические секции быстрее самопального спинлока минимум раза в 2. А то и больше - результат колебался от 2х до 4х раз. Хм...там же 4 инструкции...
Чтобы понять в чем фокус я попробывал посмотреть ассемблерный код реализации функций EnterCriticalSection и LeaveCriticalSection (если что - у меня Vista). Заодно бегло посмотрел пару статей об оптимизации ассемблерного кода под современные процессоры. А также о том что такое U-pipe, V-pipe и спаривание комманд...

Выводы:
а) код EnterCriticalSection очень похож на ручную оптимизированную правильную (и т.д.) реализацию спинлока...
б) инструкцию XCNG использовать вообще не рекомендуется.
в) чтобы написать блокировку быстрее CRITICAL_SECTION нужно очень постораться (мне это не грозит). да и то скорее всего будет работать только под конкретную архитектуру.
г) CAS все-таки быстрее. Если его не делать руками...а использовать InterlockedCompareExchangeXXX функции.

Крутил 5 потоков, вызывающих одну и ту же функцию. Считал суммарное время выполнения всех потоков. Разумеется в релизе и с оптимизацией. (там получалось что блокировка вообще не нужна, ну да это детали...)

Код с использованием CRITICAL_SECTION

void calc_synhronized_critical_section_impl()

{

    for(int i=0;i<count;++i)

    {

        EnterCriticalSection(&g_critsec);

 

        int temp = result;

        temp = temp + 1;

        result = temp;

 

        LeaveCriticalSection(&g_critsec);

    }

}



Код с "ручным" спинлоком :

void calc_synhronized_asm_spinlock_impl()

{

    static int vcookie = 0;

    LPVOID cookie = &vcookie;

    for(int i=0;i<count;++i)

    {

        __asm

        {

            mov edx, dword ptr [cookie]

            mov eax, 1

    spinLoop:

            lock xchg eax, dword ptr [edx]

            test eax, eax

            jnz spinLoop

 

        }

 

        int temp = result;

        temp = temp + 1;

        result = temp;

 

        __asm

        {

            mov edx, dword ptr [cookie]

            mov eax, 0

            lock xchg eax, dword ptr [edx]

        }

 

    }

}



Из странного и необьяснимого - если поток был один то код с ручным спинлоком оказывался быстрее...

Комментариев нет: