본문 바로가기

Robotics/Software Tech.

정확한 시간 측정, RDTSC(Read Time Stamp Counter)


윈도우에서 소프트웨어를 개발하는데 가끔 정확한 시간측정을 해야하는 경우가 있다. 디버깅레벨에서는 IDE에서 제공하는 프로파일링을 통해 특정 함수의 수행시간이나 수행빈도등을 측정하는 수도 있지만, 프로그램을 수행하여 런타임레벨에서 측정을 통해 제어를 해야할 경우가 있다.
이때, 사용하는 방법중에 RDTSC를 이용하는 것이다.

x86 P5 명령 집합들이 소개됨으로써, 많은 개임 개발자들은 높은-해상도 타이밍을 실행하기 위해 read time stamp counter (RDTSC)를 사용하도록 만들었습니다. 윈도우즈 멀티미디어 타이머들은 사운드와 비디오 처리를 위한 충분한 정밀도를 가지지만, 천분의 1초 이하의 프레임 시간들에서는 델타-시간 정보를 제공하기에 충분한 정밀도를 가지지 못합니다. 많은 게임들은 여전히 CPU의 주기를 정하도록 시작할 때 멀티미디어 타이머를 사용하고, 정확한 시간을 얻도록 RDTSC에서의 크기 결과값들을 주기 값으로 사용합니다. RDTSC의 제한으로 인하여, 윈도우즈 API는 QueryPerformanceCounterQueryPerformanceFrequency의 루틴들을 통해 이러한 기능들에 접근하는 좀 더 올바른 방법을 내놓았습니다.

세가지 근본적인 상황들로 부터 타이밍을 위한 RDTSC의 사용은 고생을 하게 됩니다 :

1. 비-지속적인 값들. RDTSC를 사용하는 것은 스레드가 항상 같은 프로세서에서 실행된다고 바로 가정을 합니다. 멀티 프로세서와 듀얼-코어 시스템들은 코어들 사이에 그들의 사이클 카운터들이 동기화 된다고 가정하지 않습니다. 이것은 다른 시간들에서 다양한 코어들을 휴지 시키고 깨우는 현대의 전원 관리 기술들과 결합될 때 코어들에서 보통 동기화가 벗어나는 결과가 발생되어 더욱 악화됩니다. 어플리케이션이서, 이것은 일반적으로 전류의 이상이나 프로세서들 사이에서 스레드 점프에 따라 가능한 충돌들내에서의 결과이고 큰 델타값, 음수 델타값, 타이밍 종료의 결과를 얻게 됩니다.

2. 전용 (dedicated) 하드웨어의 유효성. RDTSC는 프로세서의 주기 카운터에 요청을 하는 어플리케이션의 타이밍 정보를 잠그게 됩니다. 몇년동안 이것은 높은 정밀도의 타이밍 정보를 얻는 가장 좋은 방법이었지만, 새로운 마더보드들은 이제 RDTSC의 약점없이 높은 정밀도의 타이밍 정보를 제공하는 전용 타이밍 디바이스들을 포함하고 있습니다.

3. CPU 주파수의 가변성. 프로그램의 리스트에서 고정된 CPU 주파수가 만들어진다고 가정됩니다. 그렇지만, 현대적인 전원 관리 기술들로, 이것은 틀린 가정입니다. 랜탑 컴퓨터들과 다른 모바일 디바이스들에 대한 초기 제한들로, 많은 하이엔드 데스크탑 PC들에서 사용하는 CPU 주파수 변동 기술이 적용됩니다; 이러한 함수는 일반적으로 유저들에게 받아들여지지 않는 일관된 주파수를 유지하도록 하는 것이 불가능합니다.


게임들은 정확한 타이밍 정보를 필요로 할 뿐만 아니라, 개발자들 또한 RDTSC를 사용하는 것과 연관되어지는 문제들을 피하는 방법의 타이밍 코드를 구현할 필요가 있습니다. 다음의 단계들은 높은 정밀도의 타이밍을 구현할 때 적용되어져야 하는 것들 입니다.

  1. RDTSC 대신 QueryPerformanceCounterQueryPerformanceFrequency를 사용합니다. 이 API들은 RDTSC를 사용하는 것과 비슷하지만, 대신 높은 품질의 높은 정밀도 타이밍 정보를 제공하는 마더 보드나 다른 시스템 서비스들에서의 타이밍 디바이스를 사용하도록 만들게 됩니다. RDTSCQueryPerformanceCounter 보다 더욱 빠르기는 하지만, 후자는 API 호출이기 때문에, 주목할만한 효과 없이 프레임당 수백분의 몇초 단위에서 호출되어질 수 있는 API입니다 (그럼에도 불구하고, 개발자들은 그들의 게임들이 속도 약화를 피하기 위해 가능한 적게 QueryPerformanceCounter를 호출하도록 시도하여야 합니다).
  2. 델타값들 (deltas)를 계산할 때, 값들은 타이밍 값들이 충돌을 발생하지 않거나 불안정한 타임에 관련된 계산을 발생하지 않는 어떠한 버그들을 발생하지 않도록 클램프(clamped)됩니다. 클램프 범위는 0에서 (음수 델타 값들을 막도록) 당신의 최하위 프레임레이트 기대값에 기반한 논리에 맞는 값까지 입니다. 클램프는 당신의 어플리케이션의 디버깅에서 유용할 것같지만, 만일 퍼포먼스 측정이나 최적화 되지 않은 모드에서 게임의 실행한다면 주의해야 합니다.
  3. 단일 스레드에서 모든 타이밍을 계산하도록 합니다. 멀티 스레드들에서의 타이밍 계산은 - 예를 들어, 특정 프로세서와 연관된 각 스레드로 - 멀티 코어 시스템들의 속도를 굉장히 감소시키게 됩니다.
  4. Widnows API SetThreadAffinityMask를 사용하여 단일 프로세서에 단일 스레드를 남기도록 설정합니다. 일반적으로, 이것은 메인 게임 스레드입니다. QueryPerformanceCounterQueryPeroformanceFrequency는 보통 다중 프로세서들을 위해 일반적으로 조정되는 반면, 바이오스나 드라이버들에서의 버그들은 아마도 한 프로세서에서 또다른 프로세서로 스레드가 이동함에 따라서 다른 값들을 반환하는 루틴들에서 나타납니다. 그래서, 단일 프로세서에 스레드를 유지하는 것이 가장 좋습니다.
    모든 다른 스레드들은 그들의 타이머 데이터를 수집하지 않고 연산되어져야 합니다. 우리는 워커스레드를 사용하여 타이밍을 계산하는 것을 추천하지 않습니다. 이것은 동기화 병목현상이 발생할 것입니다. 대신, 워커 스레드들은 메인 스레드로부터 타임 스탬프들을 읽어야 하고, 워커 스레드들은 단지 타임 스탬프들을 읽기 때문에, 크리티컬 섹션들을 사용할 필요가 없습니다.
  5. QueryPerofrmanceFrequence은 단지 한번 호출되는데, 시스템이 실행되는 동안 주기가 변경되지 않기 때문입니다.

    원문 : http://msdn.microsoft.com/en-us/library/bb173458.aspx
    번역 : http://www.swallowstudio.com/wiki/doku.php?id=technicalarticles:gametimingandmulticoreprocessors

    - Visal Basic Example Download
    This is a example I made that shows how to get the processors actual rdtsc value in realtime. It also uses this value to calculate the clockspeed of the processor in realtime too. IT uses a .dll I made with Managed C++ (thanks to 'knight' for his help) and simply gives us the time stamp counter value.