Interlocked 함수란?

  • Interlocked 계열의 함수들은 Interlocked 계열의 함수들 간의 원자적 연산을 보장해 주는 함수입니다.




Interlocked 함수의 원리

  • x86 아키텍처의 CPU라면 메모리 버스에 Interlocked 하드웨어 시그널을 실어서 다른 코어에서 해당 캐시 라인에 접근하지 못하도록 하여 Interlocked 함수를 이용한 원자적 연산을 지원합니다.




Interlocked 함수 사용 시 주의 사항

 

주의 사항 1

  • 캐시 라인 단위로 메모리 버스를 잠그기 때문에 Interlocked 계열의 함수에 사용될 변수들은 캐시 라인에 맞추어져 있어야 원자적 연산을 보장받습니다.




주의 사항 2

  • 멀티 쓰레드 환경에서 다른 목적으로 Interlocked 계열 함수에 사용될 변수들은 서로 다른 캐시 라인에 있어야 쓰레드 경합으로 인한 성능 저하가 발생되지 않습니다.




동일 캐시 라인에 대한 쓰레드 경합 테스트

 

테스트 방법

  • 동일한 캐시 라인에 있는 두 개의 변수들을 두 개의 쓰레드를 생성하여 각 쓰레드에서 InterlockedIncrement 를 천 만번 호출할때 소요되는 시간을 측정하여 비교합니다.




테스트 코드

#include <iostream>
#include <Windows.h>
#include <process.h>

#pragma comment(lib, "Winmm.lib")

HANDLE gEvent;

CRITICAL_SECTION gCS;

unsigned __stdcall TestThread(void* p)
{
    long* pNum = static_cast<long*>(p);

    if (WaitForSingleObject(gEvent, INFINITE) != WAIT_OBJECT_0)
    {
        std::cout << "Wait Failed\n";

        return 1;
    }

    LARGE_INTEGER start;
    LARGE_INTEGER end;
    LARGE_INTEGER frequency;
    QueryPerformanceFrequency(&frequency);

    QueryPerformanceCounter(&start);

    for (int i = 0; i < 10000000; ++i)
    {
        InterlockedIncrement(pNum);
    }

    QueryPerformanceCounter(&end);

    EnterCriticalSection(&gCS);

    std::cout << (double)(end.QuadPart - start.QuadPart) / frequency.QuadPart << " 초" <<std::endl;

    LeaveCriticalSection(&gCS);

    return 0;
}

int main()
{
    timeBeginPeriod(1);

    // 64Byte 경계에 맞춤
    __declspec(align(64))
    long num1, num2;

    std::cout << "num1 주소 : " << &num1 << std::endl;
    std::cout << "num2 주소 : " << &num2 << std::endl;

    HANDLE handles[2];

    InitializeCriticalSection(&gCS);

    gEvent = CreateEvent(nullptr, true, false, nullptr);

    handles[0] = (HANDLE)_beginthreadex(nullptr, 0, (_beginthreadex_proc_type)TestThread, &num1, 0, nullptr);
    handles[1] = (HANDLE)_beginthreadex(nullptr, 0, (_beginthreadex_proc_type)TestThread, &num2, 0, nullptr);

    Sleep(2000);

    SetEvent(gEvent);

    if (WaitForMultipleObjects(2, handles, true, INFINITE) != WAIT_OBJECT_0)
    {
        std::cout << "Handles Wait Failed\n";
    }

    timeEndPeriod(1);

    return 1;
}
  • 위와 같이 코드를 작성하여 각 쓰레드에서 InterlockedIncrement 테스트를 진행 하였습니다.




테스트 결과 비교

num1과 num2는 동일 캐시라인 선상

 

num1과 num2는 다른 캐시라인 선상

 

  • 테스트 결과 약 3.5배 정도 차이 나는 것을 확인하였지만, 성능 차이 정도는 쓰레드의 개수 및 하드웨어 환경에 따라 결과는 달라질 수 있습니다.




Interlocked 계열 함수들

 

InterlockedIncrement

  • 인자로 전달된 변수를 원자적으로 1 증가시키고 return 값은 증가시킨 값입니다.




InterlockedDecrement

  • 인자로 전달된 변수를 원자적으로 1 감소시키고 return 값은 감소시킨 값입니다.




InterlockedExchange

  • 원자적으로 첫 번째 인자의 값을 두 번째 인자의 값으로 변경하고 return 값은 첫 번째 인자의 변경 전 값입니다.




InterlockedCompareExchange ( CAS )

  • 원자적으로 첫 번째 인자의 포인터가 가리키는 값과 세 번째 값을 비교하여 동일하면 첫 번째 인자가 가리키는 값을 두 번째 인자의 값으로 치환합니다. return 값은 기존에 첫 번째 인자의 포인터가 가리키는 값입니다.


  • InterlockedCompareExchange 는 Compare And Swap 으로도 불립니다.



+ Recent posts