틱
고친 사람 github-actions[bot]
틱은 프로그램 속 시간을 같은 간격으로 끊은 한 칸입니다. 프로그램은 그 한 칸마다 정해 둔 일을 한 번 합니다. 게임 서버는 틱마다 게임 속 세계를 한 걸음 앞으로 넘깁니다. 운영체제가 타이머로 시간을 셀 때도 그 한 칸을 틱이라 부릅니다.
쉽고 빠른 이해
무슨 일을 하나 — 시간을 같은 간격으로 끊습니다. 그 박자마다 할 일을 한 번 하게 합니다. 1초에 20번 박자가 오는 게임 서버는 0.05초마다 플레이어들의 입력을 모아 게임 속 세계를 한 번 고칩니다.
왜 이렇게 하나 — 게임 속 세계는 아무도 조작하지 않아도 움직입니다. 던진 공은 누군가 계속 옮겨 줘야 합니다. 입력이 올 때마다 제각각 계산하면 입력이 도착한 순서에 따라 결과가 달라집니다. 박자를 하나로 정해 두면 모든 계산이 같은 박자에 맞춰 돕니다. 같은 박자에 들어온 입력은 같은 시각에 반영됩니다.
어떻게 도나
- 박자의 간격을 정합니다. 1초에 20번이면 간격은 0.05초입니다
- 박자가 오면 그동안 들어온 입력을 모아 세계를 한 번 고칩니다
- 고친 결과를 플레이어들에게 보냅니다. 박자 번호를 하나 올립니다
대가 — 입력은 다음 박자까지 기다렸다가 반영됩니다. 박자를 촘촘히 하면 기다림이 줄어듭니다. 대신 서버가 1초에 할 계산과 보낼 데이터가 늘어납니다. 한 박자의 계산이 간격보다 오래 걸리면 게임 전체가 느려집니다. 상태가 입력으로만 바뀌는 웹 서버나 바둑 게임에는 이 대가를 치를 까닭이 없습니다.
상세
먼저 틱이 무엇인지 장면 하나로 잡습니다. 그다음 게임 서버가 틱으로 도는 까닭, 한 틱 안에서 하는 일, 화면을 그리는 박자와의 차이, 1초에 돌 틱 수를 고르는 기준을 차례로 봅니다. 끝에서는 운영체제와 시계를 다루는 코드가 같은 이름을 어떤 뜻으로 쓰는지 짚습니다.
행진하는 악대를 떠올려 봅니다. 북이 한 번 울릴 때마다 모두가 한 걸음씩 내딛습니다. 먼저 걷고 싶은 사람도 다음 북소리를 기다립니다. 줄이 흐트러지지 않는 까닭입니다.
틱(tick)은 원래 시계가 똑딱이는 소리를 흉내 낸 말입니다. 프로그램에서는 같은 간격으로 돌아오는 박자 하나를 틱이라 부릅니다. 그 박자에 맞춰 하는 일 한 번도 같은 이름으로 부릅니다. 앞의 악대로 치면 북소리 한 번이 틱입니다. 걸음 하나는 그 틱에 하는 일입니다.
예를 들어 1초에 20번 박자가 오는 게임 서버는 0.05초마다 틱 하나를 돕니다. 틱마다 날아가는 공을 한 걸음 옮깁니다. 플레이어가 그사이 누른 키도 이때 반영합니다.
같은 이름의 세 쓰임
틱이라는 말은 세 분야에서 쓰입니다. 셋 다 시간을 같은 칸으로 끊어 센다는 점에서 만납니다. 칸 하나가 무엇이고 그 칸에 무슨 일을 하는지가 다릅니다.
| 분야 | 틱 하나가 뜻하는 것 | 틱마다 하는 일 |
|---|---|---|
| 게임과 시뮬레이션 | 세계를 한 번 고치는 박자 | 입력을 모으고 세계를 한 걸음 넘긴다 |
| 운영체제 | 하드웨어 타이머가 한 번 울리는 박자 | 지난 시간을 세고 실행할 프로그램을 다시 고른다 |
| 시계를 다루는 코드 | 시계가 셀 수 있는 가장 짧은 시간 | 시각을 그 칸의 개수로 적는다 |
게임 서버가 틱으로 도는 까닭
웹 서버는 대개 요청이 올 때만 일합니다. 요청 하나를 받아 응답을 돌려주면 그 일은 끝납니다. 요청이 없으면 서버가 쥔 상태도 바뀌지 않습니다.
게임 서버는 사정이 다릅니다. 아무도 키를 누르지 않아도 던진 공은 계속 날아갑니다. 시간이 흐르는 것만으로 세계가 바뀝니다. 그래서 누군가 시간을 정기적으로 흘려보내야 합니다.
입력이 올 때마다 바로 계산할 수도 있습니다. 그러면 두 플레이어의 입력이 몇 밀리초 차이로 도착했는지에 따라 결과가 갈립니다. 같은 장면도 도착 순서가 바뀌면 다른 결과가 나옵니다.
틱은 두 문제를 함께 풉니다. 서버는 정한 간격마다 세계를 한 번 넘깁니다. 그 사이에 도착한 입력은 모아 두었다가 다음 틱에 한꺼번에 반영합니다.
한 틱에 모인 입력은 모두 같은 게임 시각에 반영됩니다. 몇 밀리초 먼저 왔는지는 그 틱 안에서 지워집니다. 세계가 틱 단위로만 바뀌니 결과도 틱 단위로 정해집니다.
한 틱 안에서 하는 일
틱 한 번은 대개 세 단계를 차례로 밟습니다. 먼저 입력을 모읍니다. 그다음 세계를 계산합니다. 마지막으로 결과를 보냅니다. 아래 그림은 플레이어 둘과 서버 하나가 101번째 틱을 지나가는 순서입니다.
sequenceDiagram
participant A as 플레이어 A
participant 서버
participant B as 플레이어 B
A->>서버: 입력 · 오른쪽으로 이동
B->>서버: 입력 · 공 던지기
Note over 서버: 틱 101 · 모인 입력을 반영하고 세계를 한 칸 넘긴다
서버->>A: 틱 101의 세계 상태
서버->>B: 틱 101의 세계 상태
서버는 틱과 틱 사이에 들어온 입력을 쌓아 둡니다. 틱이 오면 쌓인 입력을 반영합니다. 그다음 시간이 한 칸 흐른 만큼 세계를 계산합니다. 날아가던 공은 한 칸만큼 더 갑니다.
마지막으로 바뀐 세계 상태를 플레이어들에게 보냅니다. 세계 상태는 그 틱의 모든 물체의 위치와 속도 같은 값의 모음입니다. 플레이어의 화면은 이 값을 받아 그립니다.
세계를 한 칸 넘기는 계산 가운데 물체의 움직임과 충돌을 맡는 부분이 물리 스텝입니다. 서버는 틱 하나에 물리 스텝 하나를 맞추는 일이 흔합니다. 둘을 같은 말로 부르기도 합니다.
틱과 프레임
게임은 게임 루프라는 반복문 안에서 돕니다. 한 바퀴에 입력 읽기 · 세계 고치기 · 화면 그리기를 차례로 합니다.
화면에 그린 한 장이 프레임입니다. 1초에 그리는 장 수는 프레임률입니다. 프레임률은 컴퓨터 성능과 장면의 복잡도에 따라 오르내립니다.
한 바퀴에 둘이 다 들어 있어도 틱은 프레임과 따로 셀 수 있습니다. 세계를 고치는 간격을 고정해 두는 방식이 고정 타임스텝입니다. 루프는 한 바퀴 돌 때마다 지난 시간을 잽니다. 틱 간격만큼 시간이 쌓였을 때만 세계를 고칩니다.
화면을 자주 그리는 컴퓨터에서는 루프가 여러 바퀴 도는 동안 틱이 한 번도 안 돌기도 합니다. 한 바퀴가 틱 간격보다 오래 걸리는 컴퓨터에서는 한 바퀴에 밀린 틱을 여러 번 돌립니다. 1초당 틱 수는 늘 같습니다. 프레임 수만 컴퓨터마다 달라집니다. 두 박자를 견주면 이렇습니다.
| 틱 | 프레임 | |
|---|---|---|
| 하는 일 | 세계 상태를 한 칸 넘긴다 | 화면을 한 장 그린다 |
| 1초에 몇 번 | 정해 둔 수로 고정 | 컴퓨터 성능을 따라 바뀐다 |
| 어디서 도나 | 세계를 계산하는 컴퓨터(온라인 게임이면 서버) | 화면이 있는 플레이어의 컴퓨터 |
1초에 틱이 30번이고 프레임이 120장이면 틱 하나 사이에 화면이 네 장 그려집니다. 네 장이 모두 같은 그림이면 움직임이 뚝뚝 끊겨 보입니다.
보간은 두 값 사이의 값을 계산해 채우는 일입니다. 플레이어의 컴퓨터는 직전 두 틱의 결과 사이를 보간해서 네 장을 서로 다른 그림으로 그립니다.
틱레이트
1초에 도는 틱 수가 틱레이트(tick rate)입니다. 틱레이트가 높을수록 세계를 더 자주 고칩니다.
틱레이트는 헤르츠(hertz, Hz)로 적습니다. 헤르츠는 1초에 몇 번인지를 뜻하는 단위입니다. 틱레이트 20 Hz 는 1초에 틱 20번이라는 뜻입니다.
틱 간격은 1초를 틱레이트로 나눈 값입니다. 아래는 20 Hz 와 60 Hz 의 간격을 밀리초로 구한 계산입니다.
const rate = 20; // 1초에 20틱
1000 / rate; // 50 ms
const rate2 = 60; // 1초에 60틱
1000 / rate2; // 약 16.7 ms
틱레이트를 세 배로 올리면 간격은 3분의 1로 줄어듭니다. 입력이 다음 틱까지 기다리는 시간도 그만큼 줄어듭니다.
틱레이트를 공짜로 올릴 수는 없습니다. 세 가지가 함께 움직입니다.
| 틱레이트를 높이면 | 틱레이트를 낮추면 | |
|---|---|---|
| 입력이 반영되기까지 기다림 | 짧아진다 | 길어진다 |
| 서버가 1초에 하는 계산 | 늘어난다 | 줄어든다 |
| 플레이어에게 보내는 데이터 | 늘어난다 | 줄어든다 |
그래서 반응 속도가 승패를 가르는 대전 게임일수록 틱레이트를 높게 잡습니다. 한 세계에 사람이 아주 많이 모이는 게임은 서버 부담 탓에 낮게 잡기도 합니다.
틱 번호
서버는 틱을 돌 때마다 번호를 하나씩 올립니다. 게임이 시작하고 몇 번째 틱인지가 곧 게임 속 시각입니다. 틱 번호에 틱 간격을 곱하면 시각이 나옵니다. 아래는 0.05초 간격으로 1200틱을 돈 서버의 시각입니다.
const dt = 50; // 틱 간격 50 ms
const tick = 1200; // 지금까지 돈 틱
tick * dt; // 60000 ms, 1분
게임 속 시각은 소수를 더해 가지 않고 정수인 틱 번호로 셉니다. 컴퓨터가 소수를 나타내는 방식인 부동소수점은 0.05 같은 수를 딱 떨어지게 담지 못합니다. 0.05를 수만 번 더하면 작은 오차가 쌓입니다. 정수는 몇 번을 더해도 오차가 생기지 않습니다.
틱 번호는 사건에 붙이는 이름표도 됩니다. 「틱 1201에 플레이어 A가 점프했다」처럼 적을 수 있습니다.
결정성은 같은 처음 상태에 같은 입력을 넣으면 같은 결과가 나오는 성질입니다. 계산이 이 성질을 지키면 틱 번호와 입력만 적어 두어도 장면을 되살릴 수 있습니다.
게임의 리플레이를 이렇게 만들 수 있습니다. 화면을 영상으로 담지 않아도 틱마다 들어온 입력 기록만으로 경기를 다시 계산해 보여 줍니다.
틱이 간격 안에 못 끝날 때
틱 하나의 계산은 틱 간격 안에 끝나야 합니다. 20 Hz 서버라면 0.05초 안입니다. 플레이어가 몰려 한 틱에 0.08초가 걸리면 다음 틱이 제때 시작하지 못합니다.
그러면 서버는 1초에 20틱을 다 돌지 못합니다. 게임 속 시간이 바깥 시간보다 느리게 흐릅니다. 플레이어 눈에는 세계가 굼뜨게 움직입니다.
서버는 게임 속 시간을 바깥 시간에 다시 맞추려 합니다. 그러려면 밀린 틱을 한꺼번에 돌려 따라잡아야 합니다. 그 계산이 다시 간격을 넘기면 서버는 점점 더 뒤처집니다. 이 되먹임이 죽음의 나선입니다.
그래서 서버는 틱 하나에 걸린 시간을 재서 지켜봅니다. 간격을 넘기는 틱이 잦으면 틱레이트를 낮추거나 한 서버가 받는 인원을 줄입니다.
운영체제의 틱
운영체제도 틱이라는 말을 씁니다. 같은 간격으로 오는 박자 하나라는 뜻은 같습니다. 다른 점은 그 박자를 하드웨어가 만든다는 것입니다.
컴퓨터에는 정해 둔 간격마다 신호를 보내는 하드웨어 타이머가 있습니다. 운영체제는 이 신호를 박자로 삼습니다.
인터럽트는 하던 일을 끊고 급한 일을 먼저 처리하게 하는 신호입니다. 인터럽트가 오면 CPU(Central Processing Unit, 중앙 처리 장치)는 하던 일을 잠깐 멈춥니다. 그 신호를 맡은 운영체제의 코드를 먼저 부릅니다.
하드웨어 타이머가 보내는 인터럽트가 타이머 인터럽트입니다. 운영체제의 틱은 타이머 인터럽트 한 번입니다.
운영체제의 핵심부인 커널은 틱마다 두 가지 일을 합니다. 첫째는 시간 세기입니다. 부팅한 뒤 지난 틱 수를 세어 두면 틱 간격을 곱해 지난 시간을 압니다. 잠든 프로그램을 깨울 시각이 됐는지도 이때 봅니다.
둘째는 실행할 프로그램 다시 고르기입니다. 여러 프로그램이 CPU 하나를 나눠 쓸 때 한 프로그램이 CPU를 오래 붙잡으면 나머지가 멈춥니다. 그래서 프로그램마다 한 번에 쓸 시간을 정해 줍니다. 이 시간이 타임 슬라이스입니다.
커널은 틱마다 지금 프로그램이 타임 슬라이스를 다 썼는지 봅니다. 다 썼으면 다른 프로그램으로 바꿉니다. 다음에 실행할 프로그램을 고르는 이 일이 스케줄링입니다.
틱은 할 일이 없을 때도 옵니다. CPU는 쉬는 중에도 박자마다 깨어나야 해서 전기를 씁니다.
그래서 할 일이 없는 동안에는 틱을 멈추는 방식이 있습니다. 타이머는 다음 일이 있는 시각에만 겁니다. 이 방식을 틱리스 커널(tickless kernel)이라 부릅니다.
시계를 다루는 코드의 틱
시계를 다루는 코드에서 틱은 그 시계가 셀 수 있는 가장 짧은 시간을 뜻하기도 합니다. 백엔드 개발자가 흔히 만나는 예는 로그나 데이터베이스에 큰 정수로 적힌 타임스탬프입니다.
이 정수는 정해 둔 기준 시각에서 틱이 몇 개 지났는지를 셉니다. 출발점으로 삼는 이 기준 시각이 에포크입니다. 1틱이 1000분의 1초인 시계에서 에포크로부터 5000틱이 지났다면 5초가 지난 것입니다.
1틱의 길이는 시계와 라이브러리마다 다릅니다. 1000분의 1초로 세는 시계가 있습니다. 100만분의 1초로 세는 시계도 있습니다. 틱 수를 주고받을 때는 1틱이 얼마인지를 함께 알아야 합니다.
보내는 쪽이 100만분의 1초 단위로 5초를 적으면 5000000틱입니다. 받는 쪽이 이 값을 1000분의 1초 단위로 읽으면 5000초, 약 1시간 23분이 됩니다. 시각이 천 배 멀리 어긋납니다.
틱을 쓰는 곳과 안 쓰는 곳
틱은 시간이 흐르는 것만으로 상태가 바뀌는 프로그램에 맞습니다. 물체가 움직이는 게임 서버와 시뮬레이션이 그렇습니다. 정해 둔 간격으로 센서를 읽는 제어 프로그램도 그렇습니다.
상태가 입력으로만 바뀌는 프로그램에는 틱이 필요 없습니다. 한 수씩 두는 바둑이나 카드 게임은 수가 들어올 때만 계산하면 됩니다. 요청을 받아 응답하는 웹 서버도 같습니다. 이런 프로그램에 틱을 두면 아무 일 없는 박자마다 서버가 헛돕니다.
한 프로그램 안에서 둘을 섞기도 합니다. 채팅이나 아이템 구매처럼 입력에만 반응하는 기능은 요청이 올 때 처리합니다. 세계의 움직임만 틱으로 돌립니다.
관련 항목
틱을 돌리는 반복 구조와 시간 관리 방식
게임 루프 · 고정 타임스텝 · 가변 타임스텝 · 타임스텝 · 델타 타임 · 물리 스텝 · 틱 그룹
틱의 빠르기를 재는 지표
틱레이트 · 헤르츠 · 프레임률 · 프레임 시간 · 프레임 예산 · 지연
틱의 결과를 화면에 옮기는 기법
틱 번호에 기대는 네트워크 게임 기법
결정성 · 리플레이 · 락스텝 · 클라이언트 측 예측 · 롤백 넷코드 · 지연 보상
틱 시각을 담는 수 표현
부동소수점 · 고정소수점 · 정수 오버플로 · 반올림 오차
틱이 밀릴 때 나는 장애
죽음의 나선 · 스터터링 · 프레임 드롭 · 러버밴딩
운영체제가 틱으로 굴리는 장치와 기능
하드웨어 타이머 · 인터럽트 · 타이머 인터럽트 · 커널 · 스케줄링 · 타임 슬라이스 · 문맥 교환 · 틱리스 커널 · jiffies
틱을 시간 단위로 세는 시계 개념
시간과 시계 · 에포크 · 타임스탬프 · 유닉스 시간 · 단조 시계 · 시계 해상도
틱이 속하는 상위 분류
다른 이름: tick · 게임 틱 · game tick · 서버 틱 · server tick · 시뮬레이션 틱 · 클럭 틱 · clock tick · 타이머 틱