2021. 11. 8. 22:16ㆍc,c++ 기본/next step
안녕하세요 대학생 개발자입니다.
이번에는 시간과 랜덤에 대해서 정리를 좀 해보려고합니다.
음 무슨 이름이 드래곤볼에 나올거같은 방이름인데요...
우선 우리가 아는 시간 컴퓨터는 어떻게 알고있을까요?
사실 시간이라는 개념은 컴퓨터는 알수가 없죠 왜냐면 사람이 기준을 정했으니까요...
우선 1초라는게 뭔지 알아봐야 될 것 같은데요
실제 1초의 기준입니다.
"초는 세슘-133 원자의 바닥상태에 있는 두 초미세 준위간의 전이에 대응하는 복사선(방사)의 9,192,631,770 주기의 지속시간이다."
그럼 컴퓨터가 진짜 1초를 세기위해서 세슘-133원자의 바닥상태에 있는 어쩌고 저행동을 할까요?? 그럴리가없죠... 절대로...
컴퓨터가 시간을 재는 방법은 cpu가 한번 진동하는 틱이라고하죠? 클럭이요.. 그래서 컴퓨터가 사람의 시간을 정확하게 재는 방법은 특정 시간을 기준으로 해서 몇번의 틱이있었는지 그걸 재면서 시간을 정확히 측정을 합니다.
자... 벌써부터 뭔소린가 싶으신 분들도 계시겠지만 이건 온전히 제가 보고 이해하려 만든글이기 때문에... 저만 이해하면 되는 글이기때문에 그건 신경쓰지 않겠습니다 ㅋㅋㅋㅋ...
네 그렇습니다. 우리의 하드웨어들은 열심히 cpu에서 보내주는 하나하나의 클럭을 가지고 시간을 재고있는 겁니다.
사실 다들 이런 생각안해보셨나요? 우리가 운영체제를 보면 시간이 나와있는데... 이시간을 어떻게 측정하는걸까 하고... os는 코드에 불과한데 어떻게 코드주제에 시간을 측정한다는거죠? 그리고 더 나가서 c라이브러리에 있는 clock()라는 함수는 c언어 주제에 무슨수로 어떻게 시간을 측정한다는 말이죠??
다.. 이건 하드웨어로부터 시간을 받아오는겁니다.
우리의 시스템 타이머로부터 받아옵니다. 그럼 이런 고리타분하고 노잼인 얘기는 건너 뛰고 바로 우리가 직접 사용할 수 있는 함수들로 넘어가보자구요
일단 시간을 측정하기 위한 함수들로 크게 자주 쓰이는 것들은
clock, GetTickCount, GetTickCount64, timeGetTime, QueryPerformanceCounter 등이 있습니다.
이제 하나하나씩 짚어 가면서 정리를 해보겠습니다.
clock같은 경우에는 c언어에서 지원하는 함수입니다.
음... 이함수의 경우에는 process excute time을 0으로 증가해 나가는 함수고요... ms단위로 뱉어주며 해상도는 1000frame입니다.
GetTickCount 같은 경우에는 윈도우에서 지원하는 함수구요... 얘는 시스템이 시작된 시간으로 부터 측정을 시작합니다. 그니까 os에서 관리하고 있다는 의미가 되겠죠. 얘같은 경우에는 default로 해상도가 64 frame입니다... 그니까 대략 15~16ms마다 한프레임씩 나온다는 의미가 되겠죠.
음... GetTickCount를 쓰실때는 주의하실점이 몇가지 있는데요. 일단 frame이 default로 64frame이다 보니까... 15ms보다 더 작은 단위는 셀수없는 단위입니다. 그래서 그거보다 빠른건 측정이 불가능하단 말이죠. 그래서 우리는 이 값을 1로 세팅할 수 있는데요... timeBeginPeriod라는 함수입니다. 인자로 1을 넣으시면 64frame이 아니라 해상도가 1000frame으로 늘어날 거에요.
그리고 서버같이 장기적으로 켜놔야 하는 경우엔... 물론 가끔 껏다 켜겠지만... 50일 이상 안끄는 서버가 없는건 아니잖아요??
GetTickCount를 msdn으로 검색해보시면 DWORD 즉 4바이트라서 49.7일이 넘어가는 순간 오버플로가 발생할 것이라고 말해주고있어요. 이경우에는 GetTickCount64를 쓰시면 되는데요 이건 우리가 10번 죽었다가 태어나도 여전히 잘돌아갈정도로 큰 값이니까 걱정말고 이걸로 가시면 된다고 msdn에서 권고를 하고있습니다.
그리고 timeGetTime인데요 이함수도 시스템이 시작될때 돌아가는 타이머를 기반으로 하는거구요.. 해상도는 1000frame입니다. 물론 이러면 무조건 timeGetTime을 쓰는게 좋지않냐 라고 하실수도있지만... 꼭 그런건 아니에요
가벼운 함수라서 해상도가 낮은거고 무거운 함수라서 해상도가 높아지는거니까요... 뭐 상황에 맞게 잘쓰시면 될 것같아요.
서버의 경우에는 15ms보다 더 낮은 반응 속도를 요구하기 때문에 당연히 GetTickCount를 쓰겠지라고 생각하시겠지만 이것도 너무 느려요... 그래서 우리는 더 빠른걸 쓸겁니다.
바로 QueryPerformanceCounter라는 건데요...
얘는 _rdtsc라는 함수 즉 cpu클럭을 가져오는 함수에요 이걸 가지고 측정을 하기때문에 아마... 우리가 가진 그 어떤 시계보다도 정밀도가 높고 해상도가 높을거에요 나노세컨드 단위니까요(물론... 확인가능한건 100나노세컨드지만) 물론 이게 쓰기가 좀 다른함수에 비해서 복잡하긴 하지만... 그래도 이정도가 어디에요? 나노세컨드 단위로 확인할 수있다잖아요 클럭을가지고... 어마어마합니다...
사실 clock같은 함수들도 rdtscp를 이용하긴하지만... 단위를 ms로 맞춰주는 기능이 있는거 같아요... 어셈블리 파고드니까 rdtscp를 하는 부분이있더라구요...
맞아요... 그럼 아까전에 얘기했던 window나 c언어가 시간을 알수있는 이유가 하드웨어가 알려줘서라고 했는데요... 인터럽트를 통해서 알려준답니다 시스템 타이머라는 녀석이 알려주는데요 얘는 그어떤 녀석들보다 인터럽트의 순위가 높습니다 자연수에도 포함이 안되요 무려 0번입니다. 얘를 통해서 인터럽트가 들어오게되면 우리의 os는 하던일을 멈추고 쓰레드 스케쥴링을 검토하고 바꾸기도하죠.
우리의 시스템 타이머를 포함해서 모든 디바이스(키보드,마우스,마이크,그외등등)들은 모두 다 인터럽트를 기반으로 작동해요 인터럽트를 걸어주고 os가 하던일이 있더라도 그일을 제쳐두고 먼저 인터럽트가 들어온 녀석들을 실행한 뒤 바로 다시 원상태로 돌아가는거죠.
인터럽트가 들어오면 바로 그 드라이버를 실행하게 되는데 이녀석의 경우에는 NonPagedPool에 들어가구요... 이게 만약 스왑이 되는 파일이라고 한다면... 말이안되죠 ㅋㅋㅋ... cpu가 인터럽트가 왔을때 바로 실행해야되는데 없으면... 뭐 블루스크린뜨는겁니다...
아... 맞아요 그럼 시간에 대해서 했으니 날짜에 대해서 정리해볼까요?
우리가 오늘 날짜를 알고있는 이유는 0년 1월1일이 정의가 되어있기 때문일거에요... 그게아니라면 오늘의 날짜를 알 수 없죠... 컴퓨터에게도 그런날이있습니다. 바로 1970년 1월1일인데요. 그 때를 기준으로 1초씩 올라가고 있거든요... 그 시간을 Unix TimeStamp라고 부르고있구요. 뭐 우리로 따지면 BC,AD이런거라고 생각하시면 될덧...
그럼 우리는 이걸 통해서 알수있는게 하나있어요 1시간뒤의 이벤트를 처리해줘 라는 명령어가 있었고 중간에 시스템이 꺼졌다가 켜진다면... Unix Time Stamp를 이용해서 처리한다면 그 컴퓨터가 꺼졌다가 켜진다 한들 계속 잘 돌아가겠죠..
네 이렇게 열심히 시간에 대해서 적은 이유가 랜덤을 얘기해보려고 그런거였다면... 서론이 너무 길었고 아마 본론이 너무 짧아질 것 같은데요...
우린 얼른 srand와 rand라는 함수에 대해서 좀 알아봐야 할 것 같습니다.
물론 제 블로그까지 공부를 하겠다고 찾아오신 분들이라면 모두 srand와 rand의 기능은 다 알고 계실겁니다.
srand -> 시드값을 바꾸는 함수라고하는데요... 음... 잘모르겠습니다 어떻게 바뀌는지 어셈블리도 어마어마하게 길뿐더러 어셈블리를 파고파고 들어도 정확히 이해가 가지않습니다. 일단 확실한건 srand(time(NULL))을 하게되면 unix time stamp를 ecx로 받아와서 그걸 어딘가에 저장하는데... 정확히 어디다 저장하는지도 잘 모르겠습니다 일단 어딘가에 저장하는거 같습니다.
다음으로 rand함수는 쉽게 생겼죠 ㅋㅋㅋ... call 어쩌고를 하면 ecx에 시드를 받아옵니다.
시드를 받아온 다음에 214013을 더하고 10h니까... 16칸이죠 2바이트 쉬프트 라이트 해줍니다. 그리고
signed short 중 가장 큰값으로 비트마스킹을 해주고 리턴을 해주네요 즉
0~32768 사이의 값을 얻어내는 함수입니다. 네 그와 동시에 다음 시드값을 변경 해주는것 같은데요. 어떤값으로 변경해주는지는 잘모르겠습니다...
일단 확실한건 첫시드가 1인것같구요 ㅋㅋㅋ...
일단... 내부에서 이렇게 값을 관리해 주는 함수는 사실 rand만 있는게 아니에요
strtok의 경우에도 비슷한데요 처음에는 문자열을 넣고 그다음부터는 NULL을 넣게되면
내부에서 문자열을 가지고 있기 때문에 알아서 쪼개서 주는걸 알수있잖아요!
음... 그래서 제가 하고자하는 말은 srand에 유닉스 타임 스탬프를 넣어서 시간에 맞춰서 돌아가는 것으로 하자는 건데요... 가끔 예외도 있습니다. 우리가 임의의 값을 넣어야할때가 분명 존재할겁니다.
바로 리플레이와 같은 재현인데요... 일반적으로 리플레이는 다른 상황들을 다 동일하게 해두고(시드를 맞춰놓고) 플레이어의 인풋정도만을 기억해서 그상황을 다시 플레이를 하는셈이에요 결국
음... 벌써 어질어질합니다. 오늘 이내용이 제가 사실 이해하기 제일 난감했던 부분이에요 당연히 랜덤을 쓰니까 뭔말인진 알겠습니다. 근데 랜덤을 만들어 내는 과정을 알 수없게 잘 포장을 해놔서(물론 알수 있지만... 제 지식부족으로 못알아낸거) 모르겠습니다... 랜덤은 알겠으나 어째만드는지는 몰라요아아아악
음... 그럼 우리는 rand함수를 이용해서 0~32768 사이값을 얻어낼 수 있었는데요 만약 65535까지의 랜덤이 필요하다면요..?
곱하기 2를 생각하신 분은 당장 브라우저를 종료하시고 냉수마찰을 하고오시길 바랍니다. 곱하기 2를하면... 당연히 값만 2배로 늘어난거지 중간에 1칸씩 숭숭 비잖아요
덧셈으로 가능할까요?? 네 가능은합니다. 근데 케이스가 너무많죠
정답은 비트연산입니다. 쉬프트와 엔드 오어로 해결하시면 됩니다..
사실 근데 이건 랜덤함수구요...
우리가 랜덤을 다룬다고 한다면 주로 갓챠 라던가 혹은 강화확률 등 이런데서 쓰일텐데... 이런 랜덤은 좀 좋지않아요
왜냐면 10%라고 한다면 100명중 10명은 되야된다는 의미거든요... 근데 저 랜덤을 쓰게되면 100명씩 잘라서 봤을때 1번째 100명은 3명이 성공하고 두번째 100명은 11명이 성공하고 막... 들쑥날쑥하잖아요... 이건 플레이어 입장에서는 10%확률이 아닌거에요 그렇다면 우리는 어떻게 해야될까 입니다...
뭐 제가 말하는게 정답은 아니지만 저같은 케이스라면 이런식으로 구현을 할 것 같아요
만약 플레이어가 500명이고 20명만 당첨이 되야된다면 500개의 배열에 20개의 아이템을 넣고 나머지는 꽝을 넣는겁니다. 그리고 셔플할겁니다(물론 여기서 또 랜덤이 들어갑니다... 이랜덤은 어쩔수없이 그냥 rand를 써야겠죠) 그리고 나서 하나씩 꺼내주는 겁니다. 이렇게하면 구현하는 입장에서도 500명중 20명만 당첨될테니까 문제가 없을것이고. 플레이어 입장에서도 500명중 20명만 당첨될 테니까 억울하지 않을겁니다. 물론 이런게있겠죠 1~20번째가 다 당첨이라면 그뒤의 480명은 당첨이 없음에도 뽑아야되는 이런 억울한 케이스요... 하지만 어떡하겠습니까... 이게 가장 공평한 방법인걸요...
제생각에 랜덤이란건 결국 구현자가 얼마나 조작된 부분을 잘 숨기느냐의 문제인것 같아요 당연히 랜덤에 조작이 안들어갈 순 없겠죠... 하지만 그 조작이 컨텐츠적인 조작이 아니라 랜덤 자체를 그렇게 나올 수 밖에 없게 만든다면... 이걸 플레이어들이 조작이라고 생각할까요..?
음... 갑자기 철학문제가 되어버렸습니다.
그 말은 제가 이제 가야될때가 되었다는 의미죠하하하
오늘도 긴 글 읽어주신 여러분들 감사합니다.
다들 안녕히 계세요
'c,c++ 기본 > next step' 카테고리의 다른 글
DLL,LIB, FILEIO에 대한얘기 (0) | 2021.11.13 |
---|---|
게임서버 이론1 (0) | 2021.11.09 |
문자열과 해시에 대한 얘기 (0) | 2021.11.07 |
메모리에 대한 얘기 (0) | 2021.11.07 |
바이트 패딩과 캐시에 대한 얘기 (0) | 2021.11.06 |