목차
앞 글에서 소스는 CPU가 아는 말로 옮겨져야 한다고 했다. 이제 그 옮기는 일을 언제 하느냐를 본다.
번역본을 인쇄할까, 통역사를 부를까
외국어 원고를 사무실에 돌리는 방법은 크게 둘이다.
하나는 미리 통째로 번역해 인쇄해 두는 것이다. 번역가가 며칠 붙어 원고 전체를 옮기고, 그 결과물을 인쇄해 나눠 준다. 직원들은 인쇄본만 보면 되니 읽는 속도가 빠르다. 대신 원고 한 줄을 고치면 번역과 인쇄를 다시 해야 한다.
다른 하나는 읽을 때마다 통역사를 부르는 것이다. 직원이 문서를 펴면 통역사가 옆에서 한 줄씩 읽어 준다. 준비할 게 없어 지금 당장 시작할 수 있다. 대신 읽는 내내 통역사가 붙어 있어야 하고, 같은 대목을 열 번 읽으면 열 번 다시 통역한다.
앞엣것이 컴파일, 뒤엣것이 인터프리터다.
미리 옮기면 실행할 때 할 일이 없다
컴파일하는 언어는 소스를 실행 파일로 만들어 두고, 실행할 때는 그 파일을 그냥 돌린다. C, Go, Rust가 이런 식이다.
실행 시점에 옮기는 일이 하나도 안 남아 있으니 빠르다. 그리고 옮기는 쪽이 원고 전체를 미리 훑어볼 수 있어서, 더 나은 표현을 골라 다듬을 여유도 있다. 같은 계산을 두 번 하는 자리를 한 번으로 줄이거나, 절대 실행될 수 없는 부분을 아예 빼 버리는 일이 여기서 일어난다.
대가는 준비 시간이다. 한 줄만 고쳐도 다시 옮겨야 하고, 프로젝트가 커지면 이 기다림이 하루에 수십 번씩 쌓인다.
그때그때 읽으면 고치고 바로 돌려 볼 수 있다
인터프리터는 소스를 받아 그 자리에서 해석하며 실행한다. 파이썬과 자바스크립트가 이 얼굴로 시작한다.
준비 단계가 없으니 고치고 바로 돌려 볼 수 있다. 한 줄 바꾸고 새로고침, 또 한 줄 바꾸고 새로고침이 가능한 이유가 이것이다. 대화형 셸에서 코드를 한 줄씩 쳐 보는 것도 인터프리터라 되는 일이다.
대신 실행하는 내내 해석하는 일이 따라붙는다. 백만 번 도는 반복문이라면 그 안의 문장을 백만 번 다시 해석한다. 같은 대목을 열 번 읽으면 열 번 통역하는 그 대가다.
여기서 비유가 한 번 어긋난다. 사람 통역사는 문맥을 이해하고 앞뒤를 살펴 옮기지만, 인터프리터는 지금 보고 있는 그 문장만 본다. 앞에서 이 변수가 무엇이었는지, 뒤에서 어떻게 쓰일지를 넓게 보고 판단하는 능력은 오히려 미리 통째로 옮기는 쪽이 갖는다. 통역사라는 낱말은 “그 자리에서 옮긴다”까지만 빌려 쓴다.
오류를 언제 아는가
속도보다 매일 더 크게 체감되는 차이는 이쪽이다.
미리 옮기는 쪽은 원고 전체를 훑기 때문에, 옮기다가 말이 안 되는 자리를 만나면 거기서 멈추고 알려 준다. 함수 이름을 틀리게 썼거나, 수를 넣어야 할 자리에 글자를 넣었거나, 존재하지 않는 필드를 읽으려 했다면 실행하기도 전에 걸린다.
그때그때 읽는 쪽은 그 줄에 실제로 도달해야 안다. 열 갈래로 갈리는 조건문 중 한 갈래에만 오타가 있다면, 그 갈래를 타는 요청이 들어오기 전까지는 아무도 모른다. 새벽에 특정 조건에서만 터지는 사고가 여기서 나온다.
그래서 뒤엣쪽을 쓰는 팀은 그 빈자리를 다른 것으로 메운다. 테스트로 갈래마다 한 번씩 실제로 밟아 보게 하거나, 타입 검사기를 따로 얹어 실행 전에 훑게 한다. “실행 전에 훑는 눈”이 없으면 만들어서라도 붙인다는 것이 요점이다.
번역하는 일은 사라지지 않는다
둘을 놓고 “컴파일은 빌드가 느리고 실행이 빠르다”고 외우면 절반만 아는 것이다. 진짜 그림은 이렇다.
번역은 어느 쪽에도 남아 있다. 앞에 한 덩어리로 몰아두거나, 실행하는 내내 잘게 흩뿌리거나다. 몇 번째에서 뒤집히는지는 상황마다 다르고, 뒤집힌다는 것이 요점이다.
옮기는 일 자체는 어느 쪽에도 그대로 남아 있다. 앞에 한 덩어리로 몰아두거나, 실행하는 내내 잘게 흩뿌리거나가 다를 뿐이다. 그래서 판단 기준도 단순해진다.
- 한 번 만들어 오래 돌리는 것이면 앞에 몰아두는 게 이득이다. 서버는 대개 여기 속한다.
- 고치고 확인하기를 하루에 수백 번 하는 것이면 앞의 덩어리가 그대로 손실이다.
같은 팀이 개발할 때와 배포할 때 다른 방식을 쓰는 이유가 이것이다. 두 상황은 실행 횟수가 아예 다르다.
둘 중 하나가 아니라 눈금 위의 자리다
여기까지 읽고 언어를 두 무더기로 나누고 싶어지지만, 실제 언어들은 그렇게 서 있지 않다.
파이썬은 흔히 인터프리터 언어라 불리는데, 실제로는 소스를 먼저 중간 형태로 한 번 옮긴 뒤 그것을 해석한다. __pycache__ 폴더에 쌓이는 파일이 그 결과물이다. 자바는 미리 옮기는 단계가 분명히 있지만 그 결과물이 기계어는 아니고, 실행할 때 한 번 더 옮긴다. 자바스크립트는 브라우저가 읽어 가며 실행하는데 자주 도는 부분은 실행 중에 기계어로 다시 만든다.
그러니까 실제 질문은 “컴파일이냐 인터프리터냐”가 아니라 **“옮기는 일을 몇 단계로 쪼개서, 각 단계를 언제 하느냐”**다. 이 시리즈의 나머지가 그 쪼갠 단계들을 하나씩 본다.
트랜스파일은 번역인데 기계어로 가지 않는다
옮기는 일이라고 해서 도착지가 늘 기계어인 것도 아니다. 사람이 읽는 언어에서 사람이 읽는 다른 언어로 옮기는 일도 있고, 이걸 트랜스파일이라 부른다.
- 타입스크립트를 자바스크립트로
- 최신 문법으로 쓴 자바스크립트를 옛 브라우저도 아는 문법으로
- Sass를 CSS로
셋 다 결과물이 여전히 사람이 읽을 수 있는 텍스트다. 그런데도 이게 옮기는 일인 이유는 같다. 쓰는 쪽이 편한 말과 읽는 쪽이 아는 말이 다르기 때문이다. 브라우저는 타입스크립트를 모르니 누군가 옮겨 줘야 하고, 그 일이 실행 전에 일어난다.
프런트엔드에서 코드를 고치고 저장했는데 화면에 반영되기까지 잠깐 걸리는 그 시간이 대개 이 작업이다.
실무에서: 어느 쪽인지가 무엇을 바꾸나
언어를 고르는 일은 대개 우리 손 밖에 있다. 그래도 지금 쓰는 것이 눈금 위 어디쯤에 있는지 알면 판단이 달라지는 자리가 있다.
- 배포하는 물건이 달라진다 - 앞에 몰아두는 쪽은 결과물 하나만 옮기면 되고, 실행 장비에 언어가 깔려 있을 필요가 없다. 그때그때 읽는 쪽은 소스와 함께 그걸 읽어 줄 것까지 같이 있어야 한다. 배포 산출물의 크기와 준비물이 여기서 갈린다.
- 오류를 잡는 그물을 어디에 치는지가 달라진다 - 실행 전에 훑어 주는 게 없으면 그 그물을 CI 파이프라인에 직접 쳐야 한다. 타입 검사와 정적 분석을 검증 단계에 넣는 팀이 많은 이유다.
- 느린 자리를 찾는 법이 달라진다 - 앞에 몰아둔 쪽은 준비 시간이 길고 실행이 고르다. 흩뿌리는 쪽은 준비가 없는 대신 자주 도는 코드에 손실이 쌓인다. “어디가 느린가”를 물을 때 봐야 할 곳이 서로 반대편이다.
세 가지 다 언어를 바꾸자는 얘기가 아니다. 지금 쓰는 것이 옮기는 일을 언제 하는지 알면, 무엇을 대비해야 할지가 정해진다.
정리
- 옮기는 방법은 둘이다. 미리 통째로 옮겨 두거나, 실행하면서 그때그때 읽거나.
- 미리 옮기면 실행이 빠르고 전체를 보고 다듬을 수 있다. 대신 고칠 때마다 기다린다.
- 그때그때 읽으면 바로 돌려 볼 수 있다. 대신 같은 대목을 도는 만큼 되풀이해 해석한다.
- 체감이 가장 큰 차이는 오류를 언제 아느냐다. 실행 전에 훑어 주지 않으면 그 눈을 따로 만들어 붙여야 한다.
- 옮기는 일은 어느 쪽에서도 사라지지 않는다. 앞에 몰리거나 실행 내내 흩어질 뿐이고, 실제 언어들은 그 사이 어딘가에 서 있다.
다음 글은 그 “사이 어딘가”를 정면으로 본다. 소스와 기계어 사이에 한 겹을 더 끼워 넣으면 무엇이 쉬워지는지가 주제다.