
16MB 메모리에서 돌아가는 Go 언어: 세가 드림캐스트와 런타임의 미학
16MB 메모리의 세가 드림캐스트에서 Go 언어를 구동하는 Libgodc 프로젝트를 통해 본 하드웨어 제약과 소프트웨어 최적화, 그리고 런타임의 미학에 대하여.
김영태
테크리드

안녕하세요. 풀링포레스트 테크리드 김영태입니다.
개발자로 일하다 보면 어느새 풍족한 리소스 환경에 익숙해지기 마련입니다. 클라우드에 서버를 띄우고, 메모리가 부족하면 스케일업을 하고, 트래픽이 몰리면 오토스케일링을 믿고 넘어가곤 하죠. 저 역시 최근 사내 MSA(Microservices Architecture) 전환 프로젝트를 진행하면서, 효율성보다는 "일단 돌아가게 만드는 것"에 집중했던 순간들이 있었습니다. 그러다 며칠 전, 우연히 발견한 오픈소스 프로젝트 하나가 제 뒤통수를 쎄게 때리는 듯한 충격을 주었습니다.
바로 'Libgodc'라는 프로젝트입니다. 무려 1998년에 출시된 게임기, 세가 드림캐스트(Sega Dreamcast)에서 Go 언어를 구동하게 해주는 런타임입니다.
솔직히 처음에는 "그냥 재미있는 장난감이네" 하고 넘기려 했습니다. 그런데 스펙을 자세히 들여다보고는 자세를 고쳐 앉을 수밖에 없었습니다. 드림캐스트의 하드웨어 사양을 아시나요? 200MHz 싱글코어 SH-4 CPU에, 램은 겨우 16MB입니다. 운영체제(OS)도 없습니다.
우리가 흔히 사용하는 현대적인 Go 애플리케이션은 런타임 초기화에만 수십 메가바이트를 우습게 씁니다. 그런데 이 열악한 환경에서 가비지 컬렉션(GC), 고루틴(Goroutine), 채널(Channel) 같은 Go의 핵심 기능들을 모두 구현해냈다는 사실이 믿기지 않았습니다.

이 프로젝트는 표준 Go 컴파일러 대신 gccgo와 드림캐스트용 개발 키트인 KOS(KallistiOS)를 결합하여 독자적인 런타임을 구축했습니다. 단순히 "Hello World"만 찍는 게 아닙니다. 실제 하드웨어에서 측정된 성능 지표를 보면 입이 다물어지지 않습니다.
고루틴 생성(Spawn): 약 31 µs
컨텍스트 스위칭: 약 6.4 µs
GC 중지 시간(Pause): 72 µs - 6 ms
물론 최신 x86 서버에 비하면 느린 수치입니다. 하지만 20년 전 하드웨어 위에서, 그것도 OS의 도움 없이 베어메탈(Bare metal) 레벨에서 Go의 런타임 스케줄러가 작동한다는 건 경이로운 일입니다. godc라는 CLI 도구까지 제공해서 godc init, godc build, godc run 같은 명령어로 실제 개발 워크플로우를 그대로 따라갈 수 있게 해두었더군요.
이 프로젝트를 보며 뼈저리게 느낀 점은 우리가 너무 '런타임'을 블랙박스로만 대하고 있다는 사실이었습니다.
최근 저희 팀 주니어 개발자 한 분이 "고루틴은 가벼우니까 마음껏 써도 되지 않나요?"라고 물어본 적이 있습니다. 틀린 말은 아니지만, 위험한 생각이기도 합니다. 수천 개의 고루틴이 16MB 메모리 환경에서 돌아가야 한다면 어떨까요? Libgodc 개발자는 메모리 할당(Allocation) 하나하나가 하드웨어에 어떤 부하를 주는지 고민하며 코드를 짰을 겁니다. 반면 우리는 기가바이트 단위의 메모리를 낭비하면서도, 성능 저하의 원인을 엉뚱한 데서 찾고 있는 건 아닌지 반성하게 되었습니다.
특히 Gosched yield 시간이 약 120ns라는 수치를 보면서, 스케줄러가 CPU 제어권을 양보하고 다시 가져오는 비용에 대해 다시 생각해보게 되었습니다. 클라우드 환경에서도 이 원리는 똑같이 적용됩니다. 무분별한 고루틴 생성과 채널 사용은 결국 컨텍스트 스위칭 비용으로 돌아오고, 이는 전체 시스템의 레이턴시(Latency)를 증가시키는 주범이 됩니다.
저는 이 프로젝트를 계기로 현재 개발 중인 백엔드 서비스의 프로파일링 데이터를 다시 열어봤습니다. 습관적으로 사용하던 라이브러리가 불필요한 메모리 할당을 반복하고 있지는 않은지, 채널 버퍼 사이즈를 너무 안일하게 잡지는 않았는지 점검했습니다. 드림캐스트라는 극한의 제약 조건이 오히려 소프트웨어 공학의 본질인 '효율'을 다시 일깨워준 셈입니다.
개발자 여러분, 가끔은 최신 기술 스택에서 한 발짝 물러나 이런 '야생의' 프로젝트를 들여다보는 것을 추천합니다. 리소스가 무한하지 않다는 제약이 주어졌을 때, 기술이 어떻게 생존하고 최적화되는지를 관찰하는 것은 정말 큰 공부가 됩니다. 16MB 메모리에서도 Go는 훌륭하게 돌아갑니다. 우리의 코드가 느린 건, 어쩌면 언어 탓이 아니라 우리가 그 언어를 이해하려는 노력을 덜 했기 때문일지도 모릅니다.
오늘도 묵묵히 코드를 최적화하고 계신 모든 개발자분들을 응원합니다.


