Mel의 이야기
요약
본 글은 과거 컴퓨터가 드럼 메모리였던 시절, 프로그래머 '멜'이 기계어(16진수)로 직접 코드를 작성하던 시대상을 다룹니다. 멜은 컴파일러나 어셈블리어보다 낮은 레벨에서 하드웨어의 특성을 극한까지 활용하여 최적화된 프로그램을 만들었습니다.
핵심 포인트
- 과거 프로그래밍은 기계어(16진수)로 직접 작성하는 것이 일반적이었다.
- 멜은 컴파일러를 거부하고, 하드웨어에 맞춰 코드를 수동으로 최적화했다.
- 직접 최적화한 코드는 자동화된 어셈블러보다 성능이 뛰어났다.
- 컴파일러도 어셈블리어도 마다하고
16진수 기계어를 직접 작성하던 프로그래머 멜과, 그가 남긴 블랙잭 프로그램을 고치려던 동료의 이야기 - 회전하는 드럼 메모리의 타이밍까지 계산해 명령어를 배치했으며,
직접 최적화한 코드는 최적화 어셈블러보다 빨랐음 - 영업팀이 고객에게 져주는 기능을 요구하자 마지못해 구현했지만, 스위치를 켜면 오히려
컴퓨터가 매번 이기는 프로그램이 됨 - 멜이 퇴사한 뒤 수정을 맡은 동료는
종료 조건이 없는데도 정상적으로 빠져나오는 루프를 발견하고, 원리를 이해하는 데 2주를 보냄 - 기계의 특성을 극한까지 활용한 솜씨와 남이 고칠 수 없는 코드, 프로그래머의 고집이 뒤섞인
오래된 고전 글 - 이 글은
Ed Nather가 1983년 5월 21일 유즈넷(Usenet)에 게시한 「The Story of Mel」의 번역임
멜의 이야기
최근 프로그래밍의 마초적인 면을 다룬 글에서, 거침없이 단언했다.
진짜 프로그래머는 FORTRAN으로 작성한다.
요즘은 그럴지도 모르겠다.
라이트 맥주와 휴대용 계산기, ‘사용자 친화적’ 소프트웨어가 판치는 이 타락한 시대에는.
하지만 좋았던 옛날,
‘소프트웨어’라는 말부터 우스꽝스럽게 들리고,
진짜 컴퓨터가 드럼과 진공관으로 만들어지던 시절에는
진짜 프로그래머는 기계어로 작성했다.
FORTRAN도 아니고, RATFOR도 아니고, 심지어 어셈블리어도 아니었다.
기계어였다.
날것 그대로의, 꾸밈없고 불가해한 16진수 숫자들.
그걸 직접 썼다.
새로운 세대의 프로그래머들이 이 영광스러운 과거를 모른 채 자라나는 일이 없도록, 세대 차이를 어떻게든 넘어 진짜 프로그래머가 코드를 어떻게 작성했는지 설명할 의무를 느낀다.
그를 멜이라고 부르겠다.
실제 이름이 멜이었으니까.
내가 멜을 처음 만난 것은 Royal McBee Computer Corp.에 입사했을 때였다. 타자기 회사의 자회사였고, 지금은 사라진 회사다.
회사는 당시 기준으로 작고 저렴한 드럼 메모리 컴퓨터 LGP-30을 만들고 있었다. 그리고 막 RPC-4000을 생산하기 시작했다. 훨씬 개선되고, 더 크고, 더 좋고, 더 빠른 드럼 메모리 컴퓨터였다.
자기 코어 메모리는 너무 비쌌고, 어차피 오래갈 기술도 아니었다.
(여러분이 이 회사도, 이 컴퓨터도 들어본 적 없는 이유다.)
나는 이 새로운 경이로운 기계를 위한 FORTRAN 컴파일러를 만들려고 채용됐다. 멜은 그 놀라운 기계를 안내해주는 사람이었다.
멜은 컴파일러를 못마땅하게 여겼다.
“자기 코드를 다시 쓸 수도 없는 프로그램이 무슨 쓸모가 있지?”
멜은 회사에서 가장 인기 있는 프로그램을 작성한 사람이었다.
16진수로.
LGP-30에서 돌아가는 블랙잭 프로그램이었다. 컴퓨터 전시회에서 잠재 고객과 게임을 했다.
효과는 언제나 극적이었다. 전시회마다 LGP-30 부스는 사람들로 가득 찼고, IBM 영업사원들은 자기들끼리 서서 이야기를 나눴다.
그게 실제로 컴퓨터 판매로 이어졌는지는, 우리끼리 한 번도 논의하지 않았다.
멜에게 주어진 일은 블랙잭 프로그램을 RPC-4000용으로 다시 작성하는 것이었다.
(포팅? 그게 무슨 말이지?)
새 컴퓨터는 ‘1+1 주소 지정 방식’을 사용했다. 각 기계어 명령에는 연산 코드와 필요한 피연산자의 주소뿐 아니라, 회전하는 드럼 위에서 다음 명령이 있는 위치를 가리키는 두 번째 주소도 들어 있었다.
요즘 말로 하면,
모든 명령 뒤에 GO TO가 붙어 있었던 셈이다!
Pascal이여, 이 맛 좀 봐라.
멜은 RPC-4000을 좋아했다. 자기 코드를 최적화할 수 있었기 때문이다.
한 명령이 일을 마치는 순간 다음 명령이 드럼의 ‘읽기 헤드’에 막 도착해, 곧바로 실행될 수 있도록 배치하는 식이었다.
그 일을 해주는 ‘최적화 어셈블러’라는 프로그램도 있었다. 하지만 멜은 쓰기를 거부했다.
“그 녀석이 뭘 어디에 놓을지 알 수가 없잖아. 그러면 상수를 따로 둬야 한다고.”
그 말을 이해하기까지 꽤 오랜 시간이 걸렸다.
멜은 모든 연산 코드의 숫자값을 알고 있었고, 드럼 주소도 직접 정했다. 그러니 자신이 작성한 모든 명령을 숫자 상수로도 사용할 수 있었다.
가령 앞서 작성한 ‘더하기’ 명령의 숫자값이 마침 필요한 값과 같다면, 그 명령을 가져다가 곱셈에 쓸 수도 있었다.
그의 코드는 다른 사람이 수정하기 쉽지 않았다.
나는 멜이 손으로 최적화한 프로그램과, 같은 코드를 최적화 어셈블러로 다듬은 프로그램을 비교해봤다. 언제나 멜의 코드가 더 빨랐다.
당시에는 ‘하향식’ 프로그램 설계가 아직 발명되지 않았기 때문이다. 설령 있었더라도 멜은 쓰지 않았을 것이다.
그는 프로그램의 가장 안쪽 루프부터 작성했다. 그래야 드럼에서 가장 좋은 주소를 그 루프에 먼저 배정할 수 있었다.
최적화 어셈블러는 그렇게 할 만큼 똑똑하지 못했다.
멜은 시간을 지연시키는 루프도 작성하지 않았다. 말썽 많은 Flexowriter가 제대로 작동하려면 글자 하나를 출력할 때마다 잠깐 기다려야 했는데도 그랬다.
대신 다음 명령이 필요한 순간, 그 명령이 읽기 헤드를 막 지나친 위치에 있도록 드럼에 배치했다. 그러면 다음 명령을 읽기 위해 드럼이 한 바퀴를 더 돌아야 했다.
그는 이 방법에 잊을 수 없는 이름을 붙였다.
‘최적’은 ‘유일’처럼 절대적인 말이다. 그런데도 사람들은 흔히 ‘완전히 최적은 아닌’, ‘덜 최적인’, ‘그다지 최적이지 않은’ 식으로 상대적인 표현을 썼다.
멜은 가장 오래 기다려야 하는 위치를
‘가장 최악인 곳’, most pessimum이라고 불렀다.
블랙잭 프로그램을 완성하고 작동시키자 영업부에서 변경 요청이 들어왔다.
“초기화 루틴까지 최적화했어.”
그는 자랑스럽게 말했었다.
프로그램은 우아한, 물론 최적화된 난수 생성기로 ‘카드’를 섞고 ‘덱’에서 나눠줬다. 그런데 몇몇 영업사원은 게임이 너무 공정하다고 생각했다. 가끔 고객이 졌기 때문이다.
그들은 콘솔의 스위치 하나를 켜면 승률을 바꿔 고객이 이기도록 프로그램을 수정해달라고 했다.
멜은 반발했다.
명백히 부정직한 일이었다.
실제로 그랬다.
프로그래머로서 자신의 양심을 침해하는 일이었다.
그 역시 사실이었다.
그래서 거절했다.
영업 책임자가 멜을 설득했다. 사장도 나섰다. 사장의 부탁을 받은 동료 프로그래머 몇 명도 거들었다.
멜은 결국 굴복하고 코드를 작성했다. 하지만 조건을 거꾸로 넣었다. 스위치를 켜면 프로그램이 속임수를 써서 매번 이겨버렸다.
멜은 이 결과에 아주 흡족해했다. 자신의 무의식은 통제할 수 없을 만큼 윤리적이라며, 수정을 완강히 거부했다.
멜이 돈을 더 많이 주는 곳을 찾아 회사를 떠난 뒤, 사장은 내게 코드를 살펴보고 그 조건문을 찾아 뒤집을 수 있는지 물었다.
썩 내키지는 않았지만 살펴보겠다고 했다.
멜의 코드를 따라가는 일은 진정한 모험이었다.
나는 프로그래밍이 예술의 한 형태라고 느낄 때가 많았다. 그 진정한 가치는 같은 신비로운 기술에 통달한 사람만이 알아볼 수 있는 예술이다.
그 과정의 특성상, 아름다운 보석과 눈부신 묘수들이 사람들의 눈과 찬탄으로부터 감춰진 채 남는다. 때로는 영원히.
코드를 읽는 것만으로도 그 사람에 대해 많은 것을 알 수 있다.
16진수 코드라도 마찬가지다.
내 생각에 멜은 알려지지 않은 천재였다.
아마 가장 큰 충격은, 아무렇지도 않아 보이는 루프 안에 조건 검사가 전혀 없다는 사실을 발견했을 때였을 것이다.
조건 검사가 없었다.
하나도.
상식대로라면 프로그램이 영원히, 끝없이 맴도는 무한 루프여야 했다.
그런데 프로그램은 그 안으로 들어갔다가, 반대편으로 무사히 빠져나왔다.
원리를 알아내는 데 2주가 걸렸다.
RPC-4000에는 인덱스 레지스터라는 아주 현대적인 기능이 있었다.
루프 안의 명령에 인덱스 주소 지정을 사용하면, 인덱스 레지스터의 값이 명령의 주소에 더해졌다. 매번 인덱스 레지스터를 증가시키기만 하면 연속된 데이터의 다음 항목을 가리킬 수 있었다.
멜은 그 기능을 한 번도 쓰지 않았다.
대신 명령을 레지스터로 가져와 주소에 1을 더한 다음, 다시 저장했다. 그리고 수정한 명령을 레지스터에서 곧바로 실행했다.
루프는 이 추가 실행 시간까지 고려해 작성돼 있었다. 그 명령이 끝나는 순간 다음 명령이 드럼의 읽기 헤드 바로 아래에서 실행을 기다리고 있었다.
하지만 루프에는 조건 검사가 없었다.
결정적인 단서는 인덱스 레지스터 비트가 켜져 있다는 사실이었다. 명령어 안에서 주소와 연산 코드 사이에 놓인 비트였다.
그런데 멜은 인덱스 레지스터를 전혀 쓰지 않았고, 항상 0으로 남겨뒀다.
깨달음의 불이 켜지는 순간, 눈이 멀 것만 같았다.
멜은 처리할 데이터를 메모리의 맨 끝 부근, 명령으로 지정할 수 있는 가장 큰 주소 근처에 놓아뒀다.
그래서 마지막 데이터를 처리한 뒤 명령의 주소를 증가시키면 오버플로가 일어났다.
자리올림이 연산 코드에 1을 더했고, 그러면 명령어 집합에서 바로 다음 명령으로 바뀌었다.
점프 명령이었다.
아니나 다를까, 다음에 실행할 명령은 주소 0에 놓여 있었다. 프로그램은 아무 일 없었다는 듯 제 갈 길을 갔다.
나는 멜과 계속 연락하며 지내지는 않았다. 그래서 그 옛날 이후 프로그래밍 기법을 휩쓴 변화의 물결에 그가 결국 굴복했는지는 모른다.
그러지 않았으리라 생각하고 싶다.
어쨌든 나는 그 코드에 충분히 감탄한 나머지, 문제의 조건문을 찾는 일을 그만뒀다. 사장에게는 찾지 못했다고 말했다.
사장은 별로 놀라는 기색이 아니었다.
내가 회사를 떠날 때까지도, 블랙잭 프로그램은 해당 스위치를 켜면 여전히 속임수를 썼다.
그리고 나는 그게 맞다고 생각한다.
진짜 프로그래머의 코드를 마구 뜯어고치는 건
아무래도 마음이 편하지 않았다.
주석: 멜의 루프는 실제로 어떻게 작동했을까?
- Dissecting the Story of Mel은 당시 RPC-4000 매뉴얼을 바탕으로 이야기 속 기법을 분석함
- 주소의 오버플로로 연산 코드를 바꾸는 원리는 가능하지만, 인덱스 비트의 위치나 레지스터에서 명령을 직접 실행한다는 설명 등은 실제 하드웨어와 맞지 않음
- 위 번역은 회고의 내용을 그대로 옮겼으며, 해설에서는 실제로 가능했을 여러 구현 방식을 살펴봄
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기