컴퓨터의 원리 3강. 프로그램도 데이터입니다
컴퓨터가 범용 기계가 된 것은 계산이 빨라져서가 아니라, 명령어를 데이터와 같은 서랍에 넣기로 했기 때문입니다.
풀고 시작
배선에서 메모리로
에니악을 프로그래밍하는 방법을 들으면 대부분 놀랍니다. 새 문제를 풀려면 여섯 명 남짓의 기술자가 며칠에 걸쳐 케이블을 뽑아 다시 꽂고 스위치를 돌렸습니다. 계산 자체는 몇 초, 준비는 며칠이었죠. 프로그램이 기계의 몸에 새겨져 있었기 때문입니다. 1945년에 회람된 「EDVAC에 관한 보고서 초안」은 이 구조를 뒤집습니다. 명령어를 숫자로 부호화해 데이터와 같은 메모리에 넣고, 기계가 그것을 하나씩 읽어 실행하자는 제안이었습니다. 이 문서에는 폰 노이만의 이름만 적혔지만 에커트와 모클리를 비롯한 팀 전체의 논의가 녹아 있었고, 공로 귀속을 둘러싼 논쟁은 지금도 끝나지 않았습니다. 원리 자체는 이미 튜링이 1936년 논문에서 보편 튜링 기계로 제시한 것이기도 합니다. 어떤 기계의 동작 규칙을 테이프 위의 기호로 적어 두면, 그 기술을 읽는 단 하나의 기계가 모든 기계를 흉내 낼 수 있다는 발상이죠. 프로그램이 데이터가 되는 순간 컴파일러와 운영체제와 바이러스가 동시에 가능해집니다. 프로그램을 입력으로 받아 프로그램을 출력하는 프로그램이 성립하기 때문입니다.
인출하고 해석하고 실행하기
이 구조의 컴퓨터는 크게 세 덩어리로 나뉩니다. 연산과 제어를 맡는 CPU, 명령어와 데이터를 담는 메모리, 그리고 둘 사이에 값을 실어 나르는 버스입니다. CPU 안에는 다음에 실행할 명령어의 주소를 담은 프로그램 카운터가 있고, 이 값이 가리키는 곳에서 명령어를 가져오는 것부터 한 주기가 시작됩니다. 순서는 이렇습니다. 먼저 인출(fetch)로 메모리에서 명령어를 읽어 옵니다. 다음으로 해석(decode)에서 그 비트 패턴이 어떤 연산이고 어떤 레지스터를 쓰는지 제어 회로가 풀어냅니다. 마지막으로 실행(execute)에서 연산 장치가 실제 계산을 하고 결과를 레지스터나 메모리에 씁니다. 그리고 프로그램 카운터가 다음 명령어를 가리키며 처음으로 돌아갑니다. 조건 분기는 이 카운터에 다른 값을 넣는 것이고, 함수 호출은 돌아올 주소를 저장해 두는 것입니다. 우리가 아는 모든 소프트웨어가 이 단조로운 반복 위에서 돌아갑니다. 다만 현대 CPU는 이 단계들을 파이프라인으로 겹쳐 돌리고 실행 순서를 재배치하기도 하므로, 위 순서는 논리적 모형이지 물리적 시간표는 아닙니다.
폰 노이만 병목
이 구조에는 태생적인 약점이 있습니다. 명령어도 데이터도 같은 통로를 지나 CPU로 들어와야 한다는 점입니다. CPU가 아무리 빨라져도 버스가 한 번에 실어 나르는 양이 정해져 있으면, 연산 장치는 값이 도착하기를 기다리며 놀게 됩니다. 존 배커스는 1977년 튜링상 수상 강연에서 이 구조적 정체를 폰 노이만 병목이라 불렀고, 프로그래밍 방식 자체가 이 통로에 맞춰 왜곡되었다고 꼬집었습니다. 실제로 지난 수십 년 동안 프로세서 속도는 메모리 접근 속도보다 훨씬 빠르게 좋아졌기 때문에 격차는 오히려 벌어졌습니다. 대안으로 명령어와 데이터의 통로를 아예 분리한 하버드 구조가 있고, 현대 CPU도 가장 안쪽 캐시에서는 명령어용과 데이터용을 나눠 두는 절충안을 씁니다.
계층으로 속이기: 캐시와 지역성
병목을 정면으로 뚫을 수 없으니 설계자들은 다른 길을 택했습니다. 빠르고 비싸고 작은 저장소부터 느리고 싸고 큰 저장소까지 층층이 쌓아 두고, 자주 쓰는 것을 위로 끌어올리는 방식입니다.
| 층 | 특징 | 접근 속도의 감각 |
|---|---|---|
| 레지스터 | CPU 안, 수십 개 규모 | 즉시 |
| 캐시(L1·L2·L3) | CPU 근처, 여러 단계 | 매우 빠름 |
| 주기억장치(DRAM) | 본체 메모리 | 캐시보다 훨씬 느림 |
| 보조기억장치(SSD·HDD) | 전원이 꺼져도 유지 | 메모리보다 압도적으로 느림 |
이 사기가 통하는 이유는 프로그램이 착한 습관을 갖고 있기 때문입니다. 이것을 지역성(locality)이라고 부릅니다. 방금 쓴 데이터를 곧 또 쓰는 경향이 시간 지역성이고, 방금 쓴 것 바로 옆을 이어서 쓰는 경향이 공간 지역성입니다. 반복문과 배열 순회가 이 두 습관을 그대로 따르죠. 그래서 캐시는 요청된 값 하나가 아니라 그 주변 한 덩어리를 통째로 끌어옵니다. 배열을 순서대로 훑는 코드가 무작위로 건너뛰는 코드보다 훨씬 빠른 이유가 여기 있습니다. 알고리즘 교과서의 연산 횟수가 같아도 실제 속도가 갈리는 지점입니다.
무어의 법칙은 지금 어떤 상태인가
고든 무어는 1965년에 집적회로 하나에 들어가는 소자 수가 일정한 주기마다 두 배씩 늘어난다고 관측했고, 나중에 그 주기를 약 2년으로 손봤습니다. 이것은 물리 법칙이 아니라 산업의 경험칙이자 목표치였습니다. 오해를 정리해 둘 필요가 있습니다. 무어의 법칙은 속도가 아니라 집적도에 대한 진술입니다. 속도가 함께 오른 것은 데나드 스케일링이라는 별개의 경향 덕분이었는데, 소자가 작아지면 전압도 함께 낮출 수 있어 소비 전력이 그대로 유지된다는 성질이었죠. 이 성질이 2000년대 중반에 무너지면서 발열이 벽이 되었고, 클록 경쟁은 사실상 멈췄습니다. 업계의 답은 코어를 여러 개 넣는 것이었고, 그때부터 성능 향상의 부담이 하드웨어에서 소프트웨어로, 즉 병렬 프로그래밍으로 넘어왔습니다. 집적도 향상 자체는 여전히 이어지고 있지만 속도와 비용의 개선폭은 예전 같지 않다는 것이 현재의 대체적 진단입니다. 이 자원을 어떻게 나눠 쓸지는 4강 운영체제의 주제입니다.
인출 문제
생각해볼 질문 (정답 없음)
- 프로그램이 데이터가 되면서 컴파일러도 가능해졌고 악성 코드도 가능해졌습니다. 명령어와 데이터를 원천적으로 구분하는 기계를 만들면 후자를 막을 수 있을까요, 아니면 그 구분 자체가 범용성을 포기하는 일일까요.
이전: 컴퓨터의 원리 2강 · 다음: 컴퓨터의 원리 4강