WaitForSingleObject vs Interlocked*

Под WinAPI есть пара функций WaitForSingleObject() и ReleaseMutex(). Также имеется семейство блокируемых *() функций. Я решил проверить производительность между захватом одного мьютекса и обменом блокированной переменной.

HANDLE mutex;
WaitForSingleObject(mutex, INFINITE);
// ..
ReleaseMutex(mutex);

// 0 unlocked, 1 locked
LONG lock = 0;
while(InterlockedCompareExchange(&lock, 1, 0))
  SwitchToThread();
// ..
InterlockedExchange(&lock, 0);
SwitchToThread();

Я измерил производительность между этими двумя методами и обнаружил, что использование Interlocked*() примерно на 38% быстрее. Почему это так?

Вот мой тест производительности:

#include <windows.h>
#include <iostream>
#include <conio.h>
using namespace std;

LONG interlocked_variable   = 0; // 0 unlocked, 1 locked
int run                     = 1;

DWORD WINAPI thread(LPVOID lpParam)
{
    while(run)
    {
        while(InterlockedCompareExchange(&interlocked_variable, 1, 0))
            SwitchToThread();
        ++(*((unsigned int*)lpParam));
        InterlockedExchange(&interlocked_variable, 0);
        SwitchToThread();
    }

    return 0;
}

int main()
{
    unsigned int num_threads;
    cout << "number of threads: ";
    cin >> num_threads;
    unsigned int* num_cycles = new unsigned int[num_threads];
    DWORD s_time, e_time;

    s_time = GetTickCount();
    for(unsigned int i = 0; i < num_threads; ++i)
    {
        num_cycles[i] = 0;
        HANDLE handle = CreateThread(NULL, NULL, thread, &num_cycles[i], NULL, NULL);
        CloseHandle(handle);
    }
    _getch();
    run = 0;
    e_time = GetTickCount();

    unsigned long long total = 0;
    for(unsigned int i = 0; i < num_threads; ++i)
        total += num_cycles[i];
    for(unsigned int i = 0; i < num_threads; ++i)
        cout << "\nthread " << i << ":\t" << num_cycles[i] << " cyc\t" << ((double)num_cycles[i] / (double)total) * 100 << "%";
    cout << "\n----------------\n"
        << "cycles total:\t" << total
        << "\ntime elapsed:\t" << e_time - s_time << " ms"
        << "\n----------------"
        << '\n' << (double)(e_time - s_time) / (double)(total) << " ms\\op\n";

    delete[] num_cycles;
    _getch();
    return 0;
}

2 ответа

Решение

WaitForSingleObject не должен быть быстрее. Он охватывает гораздо более широкий диапазон сценариев синхронизации, в частности, вы можете ожидать дескрипторы, которые не "принадлежат" вашему процессу и, следовательно, синхронизируют между процессами. Принимая все это во внимание, это всего лишь на 38% медленнее, согласно вашему тесту.

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

Кроме того, вы можете взглянуть на API-интерфейс Slim Reader/Writer (SRW). Возможно, вы сможете построить аналогичный класс / функции, основанные исключительно на InterlockedXxx с немного лучшей производительностью, однако, дело в том, что с SRW вы получаете его готовым к использованию "из коробки", с документированным поведением, стабильным и с достойной производительностью в любом случае.

Вы не сравниваете эквивалентные блокировки, поэтому неудивительно, что производительность так отличается.

Мьютекс допускает межпроцессную блокировку, это, вероятно, один из самых дорогих способов блокировки из-за гибкости, которую он обеспечивает. Обычно он переводит ваш поток в спящий режим, когда вы блокируете блокировку, и это не использует процессор, пока вы не проснетесь, получив блокировку. Это позволяет другому коду использовать процессор.

Ваш код InterlockedCompareExchange() является простой спин-блокировкой. Вы будете сжигать процессор в ожидании вашей блокировки.

Вы также можете рассмотреть критические разделы (меньше накладных расходов, чем Mutex) и тонкие блокировки чтения / записи (которые можно использовать для взаимного исключения, если вы всегда получаете эксклюзивную блокировку и которые обеспечивают незначительно более высокую производительность, чем критические разделы для не оспариваемых использовать, согласно моим тестам).

Вы можете также прочитать "Эволюция синхронизации в Windows и C++" Кенни Керра и материал, связанный с блокировками Preshing, здесь и здесь.

Другие вопросы по тегам