Sleep 성능 공부

이민석·2024년 11월 14일

Sleep은 스레드가 내가 지정한 시간만큼 재운다고 알고 있었다.

흠.. 그렇다면 자세히 봐보자.

일하는 상태(Running)였던 스레드가 Sleep을 만나면 일을 중지한 상태(Suspend)로 변하게 된다.

Sleep == Running -> Suspend


이 것이 중요한 점은, Running상태가 아니다. 즉, CPU 스케줄링 과정에서 제외가 된다.
이 후, 내가 정해진 시간만큼 쉬고나서 다시 스케줄링에 참여하게 된다.

다시 설명하자면

제외되는 시간 + Sleep에 요구한 시간 + 스케줄링에 투입되는 시간

스레드 컨텍스트 스위칭이 발생하기 때문에 정확한 시간만큼 Sleep을 시키기가 어렵게 된다.

테스트를 해보면,

chrono::high_resolution_clock과 sleep_for 사용

ns 적용

ms적용 (ns에 적용할 시에는 timeBeingPeriod를 적용안해도 됐는데 여기서는 적용을 해야지만 윈도우해상도 제한을 풀 수 있었음..)


chrono::high_resolution_clock과 sleep_until 사용

ns적용

ms적용


QueryPerformanceFrequency와 Sleep_for 사용

ns 적용

ms 적용


QueryPerformanceFrequency와 Sleep_until 사용

ns적용

ms 적용


특이했던 점은 chrono를 이용해서 ns를 Sleep으로 넣으면 윈도우 해상도를 명시적으로 해주지 않아도 알아서 제한을 풀어주었다는 점.

sleep_until 내부에서도 최적화 로직이 있는지는 잘 모르겠다는 점. 우선 더 파봐야할 것 같다.

결론적으로 Sleep은 스레드 스케줄링에서 제외되는 것이 필연적으로 발생하고, 1ms와 같이 정밀한 시간측정에서는 오차를 보일 확률이 크다.

그래서 BusyWaiting기법을 사용해보았다.
간단하게, 아주 짧은 시간이므로 while문을 돌면서 대기하는 것이다. 시간이 늘어난다면 이 스레드가 불필요하게 오래 물고 있기에 비효율적이지만 짧은 시간에서는 컨텍스트스위칭을 발생시키지 않으므로 정밀한 시간측정이 가능해진다.

실험적으로도 결과는 좋았다.

오차가 굉장히 줄면서 성능이 좋아졌다. BusyWaiting의 구현은 다음과 같이 간단히 했다.

아주 정밀한 시간에는 BusyWaiting을 쓰면 좋을 것 같다.


이를 통해서 효율적인 sleep을 만들 수도 있겠다는 생각이 드는데 C++에서 정렬은 Intro정렬 알고리즘을 사용한다.

퀵정렬

가장 좋은 성능을 가졌지만 정렬된 값을 퀵정렬에 보내면 최악의 성능을 낸다.
그러기에 C++에서는 재귀 호출 깊이가 커진다면(log n의 2배) 다른 정렬 알고리즘을 사용.

힙정렬

위와 같은 경우가 발생하면 항상 O(n log n)으로 보장되는 힙정렬을 사용하게 됩니다.

삽입 정렬

사실 핵심은 아니고 마지막 과정에 와서 분할된 배열이 작아진다면 삽입정렬의 효율이 늘어서 사용하게됨.

이와 같이 여러 알고리즘을 내부적으로 가지고 있기에 좋은 성능을 가집니다.

이와 같이 sleep 또한 내부적인 알고리즘을 만들면 좋지 않을까 생각.

0 ~ 2ms

BusyWaiting 사용 - 정확한 시간 측정이 필요하기에 BusyWaiting을 사용하는 것이 효율적일 것 같음.

2ms ~ 10ms

짧긴 하지만, BusyWaiting 만을 사용하기에는 CPU자원을 오래 문다고 생각할 수 있음.
컨텍스트 스위칭이 고려되는 시간(2ms)을 남긴 채, Sleep을 사용. 그 후, 남은 시간을 BusyWaiting으로 대기
예시) EffectiveSleep(10); -> Sleep(8) + BusyWaiting(2)

10ms 이상

스레드 컨택스트스위칭에 의한 영향을 받지 않을 정도의 대기라고 판단. Sleep을 사용.

profile
게임개발이 하고픈 이민석입니다.

0개의 댓글