옛날 게임, 8KB 램으로도 갓겜된 비결! 레전드 최적화 기술 대공개
옛날 게임 개발은 왜 이렇게 힘들었을까? (중학생 눈높이 설명)
게임을 하려면 컴퓨터 부품 중에 램(RAM)이라는 게 꼭 필요해. 왜냐면 게임에 필요한 데이터들을 램에 올려놔야 CPU라는 똑똑한 부품이 그걸 가져다가 계산할 수 있거든. 요즘 게임들은 그래픽도 엄청 좋아서 램을 엄청 많이 잡아먹기도 해.
근데 옛날에는 지금처럼 램이 넉넉하지 않았어. 포켓몬 레드라는 명작 게임을 돌리던 게임보이라는 게임기를 생각해 봐.
- CPU 성능: 지금이랑 비교하면 거의 쓰레기 수준이었대. 코딩 잘못하면 게임기가 터질 뻔했다고 하니 말 다 했지.
- 램 용량: 겨우 8KB (킬로바이트) 밖에 안 됐어. 지금 워드 파일 한 문장 쓰는 것보다도 적은 용량이야.
그래서 옛날 개발자들은 코딩할 때 엄청난 최적화 스킬을 써야 했어.
포켓몬 도감, 메모리를 아껴 써야 했어요!
포켓몬 게임에는 도감 기능이 있어서, 어떤 포켓몬을 만났는지 기억하고 세어주는 기능이 있었잖아. 1세대 포켓몬이 총 151마리인데, 얘네를 만났는지 안 만났는지 전부 메모리에 기록해야 했어.
어떻게 기록했을까?
-
일반적인 방법 (비효율적):
- 포켓몬 151마리 각각에 대해 "만났다" 또는 "안 만났다"를 기록해야 해.
- 보통 이런 정보는 참/거짓 (True/False) 형태로 저장하는데, 이게 1바이트를 차지한다고 가정해 보자.
- 그럼 151마리니까 총 151바이트가 필요해.
- 근데 게임보이 전체 메모리에서 이 기능 하나에 2%나 써버리는 건 너무 아깝잖아?
-
옛날 개발자들의 방법 (효율적):
- 비트(bit)라는 걸 이용했어. 1바이트는 8개의 비트로 쪼갤 수 있는데, 각 비트는 0 또는 1만 저장할 수 있어.
- 그래서 1번부터 8번 포켓몬까지 만났는지 여부를 첫 번째 비트에 저장하고, 9번부터 16번까지는 다음 비트에 저장하는 식으로 쪼갠 거야.
- 예를 들어, 1번 포켓몬을 만났으면 해당 비트를 1로 바꾸는 거지.
- 이렇게 하면 151마리를 기록하는 데 약 19바이트 정도만 필요하게 돼! 훨씬 적은 메모리로 같은 기능을 구현할 수 있었던 거지.
곱셈도 코딩으로 직접 만들어야 했다고?
요즘 컴퓨터는 CPU 안에 곱셈, 나눗셈 같은 계산을 해주는 부품이 다 들어있어. 근데 옛날 CPU에는 이런 게 없었대. 그래서 코딩할 때 곱셈, 나눗셈 같은 문법을 못 썼어.
곱셈을 하려면 어떻게 했을까?
- 덧셈 반복: 10에 5를 곱하고 싶으면, 10을 다섯 번 더하는 코드를 짜는 거야. (10 + 10 + 10 + 10 + 10)
- 근데 만약 10에 50이나 100을 곱해야 한다면? 덧셈을 50번, 100번 반복해야 하니 CPU가 터져나갈 거야.
그래서 옛날 개발자들은 아주 신기한 곱셈 방법을 직접 만들어서 썼어.
-
준비:
- 곱셈 당할 숫자(예: 10)를 변수에 저장해 둬.
- 곱할 숫자(예: 5)를 이진수로 바꿔 둬. (이진수는 0과 1로만 이루어진 숫자야)
-
계산:
- 이진수로 바꾼 숫자에서 가장 오른쪽 비트부터 하나씩 빼봐.
- 만약 빼낸 비트가 1이면, 결과에 아까 변수에 저장해 둔 숫자를 더해줘.
- 만약 빼낸 비트가 0이면, 그냥 넘어가.
- 비트를 하나씩 뺄 때마다 변수에 저장된 숫자에 2를 곱해줘. (이게 핵심이야!)
이런 식으로 비트를 하나씩 빼면서 계산하면, 곱할 숫자가 아무리 커도 최대 8번의 비트 처리만으로 곱셈이 끝나는 거야. 훨씬 효율적이지?
근데 여기서 이상한 점! 2를 곱하는 걸 썼잖아? 곱셈을 구현하는데 곱셈을 쓴다니 이상하지?
실은 2를 곱하는 건 비트를 왼쪽으로 한 칸만 옮기면 돼! 마치 숫자를 왼쪽으로 한 칸씩 미는 것처럼 말이야. 반대로 2로 나누는 건 비트를 오른쪽으로 한 칸 옮기면 되고. CPU는 이런 비트 옮기는 걸 엄청 빠르게 할 수 있거든. 그래서 옛날에는 2의 거듭제곱(2, 4, 8, 16...)을 많이 썼던 거야.
상태 이상도 비트로 관리했어요!
포켓몬이 걸릴 수 있는 상태 이상(마비, 독 등)도 메모리에 저장해야 했어. 이것도 각각 1바이트씩 쓰면 너무 아까우니까, 하나의 바이트를 8개로 쪼개서 각 비트에 상태 이상을 저장했어.
- 예를 들어, 마비에 걸리면 특정 비트를 1로 바꾸고, 중독되면 다른 비트를 1로 바꾸는 식이지.
이게 왜 좋았을까?
- 엄청 빠른 비교: 내 포켓몬이 마비 또는 중독 상태인지 확인하고 싶을 때, 옛날처럼
if문을 여러 개 쓰는 게 아니라, AND 연산자를 써서 한 번에 확인할 수 있었어. - AND 연산자: 두 숫자의 비트를 비교해서, 같은 위치의 비트가 둘 다 1일 때만 1을 남기는 연산자야.
- 마비와 중독 비트 위치에 1이 채워진 숫자를 만들어 놓고, 현재 포켓몬 상태와 AND 연산을 하면, 마비나 중독 상태가 있다면 결과에 1이 남게 되는 거지. 이걸로 순식간에 상태 이상을 체크할 수 있었던 거야.
그래서 요즘은 왜 안 할까?
옛날에는 램도 부족하고 CPU 성능도 낮아서, 개발자들이 램과 CPU를 손으로 어루만지듯이 코딩해야 했어.
하지만 요즘은 컴퓨터 부품들이 엄청 좋아졌고, 메모리도 충분하고, CPU 캐시도 크고, 컴파일러라는 프로그램이 코드를 알아서 최적화해줘. 그래서 여러분이 코드를 좀 대충 짜도 알아서 성능 좋게 바꿔주기 때문에, 굳이 옛날처럼 복잡하게 코딩할 필요가 없어졌지.
그래도 옛날 개발자들이 얼마나 대단했는지 알겠지?