왜 굳이 옛날 CPU에서 압축을 풀까
요즘 우리가 쓰는 압축 알고리즘 중에 LZ4라는 게 있어요. 이게 뭐냐면, 압축률보다 속도에 극단적으로 초점을 맞춘 알고리즘이에요. zip이나 gzip은 파일을 작게 만드는 데 신경 쓰지만, LZ4는 "좀 덜 줄어들어도 좋으니 빠르게 풀어달라"가 목표거든요. 그래서 게임 엔진, 데이터베이스, 메신저, 게다가 리눅스 커널의 zram(메모리 압축)까지, 실시간성이 중요한 곳에 두루 쓰여요.
이번에 한 개발자가 LZ4 디컴프레서(압축 푸는 코드)를 네 종류의 옛날 CPU에서 돌려보고 성능을 비교한 글을 올렸어요. 대상은 모토로라 68000(8090년대 매킨토시·아미가·세가 메가드라이브의 그 칩이에요), 자일로그 Z80(MSX·게임보이 계열), 6502(애플 II·NES), 그리고 인텔 8088(원조 IBM PC) 같은 칠팔십년대 CPU들이에요. "왜 굳이?" 싶지만, 레트로 게임 개발이나 임베디드 개발에서는 여전히 중요한 주제고요. 무엇보다 느린 CPU에서 알고리즘을 보면 병목이 적나라하게 드러나서, 최적화 공부에 이만한 게 없어요.
LZ4가 빠른 이유와 옛 CPU의 한계
LZ4의 핵심 아이디어는 단순해요. 압축된 데이터를 읽다가 "여기서부터 N바이트는 앞쪽 어디서 본 것과 똑같다"는 신호를 만나면, 그냥 그 위치에서 N바이트를 메모리 복사(memcpy)로 가져와 붙이는 거예요. 복잡한 비트 단위 디코딩 없이 길이 정보와 오프셋만 읽고, 나머지는 통째로 옮기는 방식이라 현대 CPU에선 캐시와 SIMD 덕분에 거의 메모리 대역폭 한계까지 속도가 나와요.
그런데 옛 CPU엔 이게 사정이 달라요. 68000은 16/32비트 레지스터와 비교적 풍부한 명령어 세트를 갖춰서 "느리지만 합리적"인 결과가 나와요. 한 번에 워드(2바이트)씩 옮길 수 있고 인덱스 어드레싱도 깔끔하거든요. 반대로 6502는 레지스터가 A, X, Y 세 개뿐이고 각각 8비트라서, 16비트 오프셋을 다루려면 두 번 나눠 처리해야 하고 페이지 경계를 넘는 순간 사이클이 추가돼요. 그래서 같은 코드라도 6502에선 압축 해제 속도가 극단적으로 떨어져요.
Z80은 또 다른 매력이 있어요. LDIR이라는 블록 복사 전용 명령어가 있어서, "이 주소부터 저 주소로 N바이트 복사"를 한 줄에 해결해요. LZ4의 핵심이 메모리 복사라는 점을 생각하면, Z80은 의외로 LZ4와 궁합이 좋은 셈이에요. 8088은 세그먼트 레지스터를 다뤄야 하는 번거로움이 있지만 REP MOVSB 같은 문자열 명령어로 비슷한 이점을 누릴 수 있고요. 글쓴이는 각 플랫폼별로 어셈블리를 손으로 최적화하면서, 명령어 사이클 표와 씨름하며 1바이트당 사이클 수까지 측정했어요.
성능 측정에서 드러나는 패턴
결과를 거칠게 요약하면, CPU가 한 번에 옮길 수 있는 바이트 수와 메모리 인터페이스 폭이 모든 걸 결정해요. 68000은 32비트 레지스터지만 16비트 데이터 버스라서 이론치만큼 빠르진 않고, 8088도 16비트 CPU지만 8비트 외부 버스 때문에 발목이 잡혀요. Z80과 6502는 둘 다 8비트지만, 블록 복사 명령어 유무에서 큰 차이가 나요.
또 하나 흥미로운 건 분기 비용이에요. 현대 CPU는 분기 예측이 잘돼서 if-else가 거의 공짜처럼 느껴지지만, 옛 CPU는 분기 한 번에 10사이클이 훌쩍 넘는 경우가 흔해요. 그래서 LZ4 디코더에서 "길이가 짧을 때"와 "길이가 길 때"를 분리한 빠른 경로(fast path)를 만드는 것이 성능에 결정적이에요. 이건 현대 코드에서도 똑같이 통하는 원칙이지만, 옛 CPU에선 그 효과가 10배 이상으로 증폭돼서 보여요.
비슷한 시도, 그리고 산업 맥락
이런 "옛 CPU에서 현대 알고리즘 돌리기"는 단순한 취미가 아니에요. 레트로 게임 리메이크 씬에서는 카트리지 용량이 한정돼 있어서 압축이 필수예요. NES에 새 RPG를 만들거나 메가드라이브용 신작 게임을 만드는 인디 개발자들은 실제로 LZ4, aPLib, Exomizer 같은 압축 라이브러리의 "옛 CPU 포트"를 활발히 비교하고 있어요. 또 임베디드 펌웨어에서도 비슷해요. ARM Cortex-M0 같은 저가형 마이크로컨트롤러는 사실상 8088과 비슷한 수준의 자원만 갖추고 있거든요.
현대 서버 영역에선 LZ4보다 더 빠른 Zstandard(zstd) 가 표준 자리를 차지해 가고 있지만, 이건 SIMD와 큰 캐시를 전제로 한 알고리즘이에요. 자원이 빠듯한 환경에선 여전히 LZ4가 왕좌를 지켜요.
한국 개발자가 얻을 수 있는 것
저전력 IoT 디바이스나 차량용 ECU, 그리고 게임 클라이언트의 에셋 로딩 등을 다루는 분이라면, 이런 분석은 그냥 "옛날 이야기"가 아니에요. CPU 사이클 단위로 알고리즘을 들여다보는 감각은 요즘처럼 추상화가 두꺼운 시대에 점점 희귀해지고 있는데, 이런 글을 한 번 따라 읽으면 "왜 캐시 친화적인 코드가 빠른지", "왜 분기를 줄여야 하는지"가 손에 잡혀요. 평소 우리가 무심코 호출하는 memcpy 하나가 실제로 얼마나 정교한 최적화 덩어리인지 느낄 수 있는 좋은 기회예요.
마무리
빠른 컴퓨터에선 알고리즘의 약점이 가려지지만, 느린 컴퓨터에선 모든 게 드러난다는 걸 다시 확인하는 글이에요. 여러분은 마지막으로 어셈블리 단위 최적화를 해본 게 언제인가요? 혹은, 요즘 시대에 그게 여전히 가치 있다고 생각하시나요?
🔗 출처: Hacker News
"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"
실제 수강생 후기- 비전공자도 6개월이면 첫 수익
- 20년 경력 개발자 직강
- 자동화 프로그램 + 소스코드 제공