튜링상 관련 업적

컴파일러의 설계와 이론, 대형 시스템의 구조, RISC 개발에 중요한 기여를 했다. 연산 강도 경감, 공통 부분식 제거, 레지스터 할당, 상수 전파, 죽은 코드 제거 등을 포함하여 컴파일러 최적화에 현재 사용되고 있는 다수의 근본적인 기법을 발견하고 체계화하였다.

1987년 튜링상 선정 이유​3​

존 코크의 업적을 설명하기에 앞서 한 가지 짚고 넘어가야 할 부분이 있다. 존 코크는 박사 학위를 받은 후에 평생을 IBM에서 일했다. 그리고 그는 실제 동작하는 시스템을 만드는 일에 주로 참여했다. 그의 이름으로 발표된 논문이 적은 이유이다. 또한, 팀에 속해서 일했기 때문에 단독으로 결과물을 내는 일도 드물었다. 그렇기 때문에 아래에 설명될 기술들에 대한 공을 그에게만 돌리기에는 모호한 측면이 있다. 그가 처음으로 이런 아이디어를 냈는지도 확실치 않고, 그의 아이디어가 오로지 그의 것인지도 단언하기 힘들지만, 그가 중요한 기여를 했음은 분명하다고 봐야겠다.

스트레치 컴퓨터

컴퓨터 프로세서와 사이클 시간

미리보기lookahead 기법은 스트레치 컴퓨터에 처음 사용된 것으로 알려져 있다. 미리보기 기법이 도입된 배경을 이해하려면 폰 노이만 구조를 알아야 한다.

폰 노이만 구조란, 프로그램 코드와 데이터가 모두 메모리에 저장되는 방식의 컴퓨터 구조를 말한다. 흔히 저장형 프로그램 방식의 컴퓨터라고도 부른다. 사실, 우리가 일반적으로 접하는 거의 대부분의 컴퓨터가 폰 노이만 구조이기 때문에, 이게 왜 특별한지 이해하기 어려울 수도 있다. 하지만 1940년대 말에 저장형 프로그램 방식의 컴퓨터가 등장하기 전에도 이미 전자식 컴퓨터가 있었음을 기억할 필요가 있다. ENIAC이 대표적이다. ENIAC은 회로의 배선(연결)을 바꾸는 방식으로 프로그래밍을 했다.

다음과 같은 어셈블리어 코드를 생각해 보자.

ADD @100 200 R1

@는 뒤에 오는 숫자가 메모리 번지임을 의미한다. 위의 코드는 100번지에 저장된 값과 상수 200을 더한 후에 그 결과를 R1 레지스터에 저장하라는 의미를 가진다. 이 코드는 설명을 위해서 임의로 만든 것이지 실제로 이런 어셈블리어는 존재하지 않음에 유의하자.

위의 어셈블리어 코드에서는 한 개의 메모리 번지를 직접적으로 명시하고 있다. 하지만 이 코드와 관련된 보이지 않는 번지가 하나 더 있다. 그것은 이 코드가 저장된 메모리 번지이다. 폰 노이만 구조에서는 프로그램 코드가 메모리에 저장되어 있기 때문에 이 코드가 실행된다는 것은, 이 코드가 메모리의 어딘가에 저장되어야 한다는 의미를 포함하고 있다.

폰 노이만 구조에서 프로그램의 수행은, 어떤 약속된 메모리 주소에서부터 시작해서 차례차례 다음 주소에 있는 코드(기계어)를 실행함을 의미한다. 이를 위해서는, 어떤 약속된 메모리 주소에서부터 시작해서 기계어 코드를 차례로 읽어 오는 하드웨어 구조가 필요하다. 보통 기계어 코드의 길이는 특정 값으로 정해져 있다. 예를 들어, 기계어 코드의 길이가 4바이트이고 첫 시작번지가 0번지라고 하면, 0번지, 4번지, 8번지, 12번지 … 와 같은 순서로 메모리를 접근해서 기계어 코드를 읽어와야 한다. 이 동작을 위해 프로세서에는, 이 번지값을 증가시키고 관리하는 회로가 존재한다. 이를 부르는 이름은 회사마다 가지각색인데 여기서는 편의상 ‘시퀀싱 회로sequencing unit‘라고 부르겠다. 시퀀싱 회로는 말 그대로 다음 차례인 기계어 코드의 주소를 결정하는 회로이다. 앞의 예에서 다음 차례의 기계어 코드는 단순하게 주소를 4만큼 증가시키면 되는 듯 보인다. 하지만 실제는 좀 더 복잡하다. 현재 수행되고 있는 기계어 코드가 분기branch 명령이라면 분기 명령에 포함된 주소값으로 바뀌어야 하기 때문이다. 만약 이 분기 명령이 어떤 조건에 의해 결정된다면 해당 조건을 판별하는 작업도 해야 한다.

그런데 프로세서가 메모리에 접근하는 경우는 기계어 코드를 가져올 때만이 아니라 데이터를 가져올 때도 발생한다. 앞에서 예로 들었던 어셈블리어 코드를 보자. 그 코드가 메모리의 8번지에 저장되어 있었다고 가정해 보자. 중간에 분기 명령이 없다고 가정한다면 프로세서의 ‘시퀀싱 회로’는 0번지로 처음 출발해서 4번지, 8번지로 증가해 갈 것이다. 그래서 ‘시퀀싱 회로’가 8번지를 가리킬 때 메모리에서 위의 어셈블리어 코드를 읽어 와서 수행한다. 그런데 이 코드는 100번지에 저장된 값을 읽어서 숫자 200을 더한 후에 R1 레지스터에 저장해야 한다. 100번지에 저장된 값을 가져오려면 메모리를 접근해야 한다. 이렇듯 기계어 코드 내에서 데이터를 위한 메모리 접근을 하려면 이때의 주소를 관리하는 회로가 필요하다. 이를 부르는 이름은 여러 가지가 있으며 조금씩 세부적인 기능은 다르기도 한데 여기서는 ‘인덱싱 회로indexing unit‘라고 부르겠다. 원래 인덱싱이라는 표현은 배열과 같이 규칙적으로 메모리에 저장된 데이터를 접근할 때 주로 사용되기는 하지만, 여기서는 그런 기능을 모두 포함해서 기계어 코드 내의 오퍼랜드operand와 관련된 메모리 주소를 관리하는 것으로 생각하겠다.

좀 정리하자면 프로세서가 메모리에 있는 기계어 코드를 차례로 수행하기 위해서는 메모리 주소를 관리하는 두 개의 회로를 가지게 된다. 시퀀싱 회로는 기계어 코드의 주소를 처리하며, 인덱싱 회로는 기계어 코드가 접근하는 메모리 주소를 처리한다. 이제 컴퓨터 프로세서의 나머지 남은 한쪽은 산술논리연산처리 회로이다. 흔히 ALU라고 부르는 것으로 덧셈, 뺄셈, 곱셈, 나눗셈 등과 같은 산술연산과 AND, OR, NOT 등과 같은 논리연산을 처리한다.


이제 컴퓨터 프로세서의 세 가지 구성 요소를 확인했으니 프로세서가 어떻게 동작하는지를 따져보자. 프로세서는 같은 일을 계속 반복한다. 여기서 말하는 ‘같은 일’이란 메모리에서 기계어 코드를 읽어와서 실행하는 일이다. 알고 보면 단순 작업의 반복이다. 이렇게 반복되는 ‘같은 일’을 ‘사이클cycle‘이라고 부른다. 컴퓨터의 성능은 프로그램을 얼마나 빨리 끝내냐에 달려 있다. 프로그램이란 결국 기계어 코드의 집합이므로 프로그램의 수행시간은 프로그램을 구성하는 기계어 코드의 개수에 비례하고 기계어 코드 처리 시간, 즉 사이클 시간에 비례한다. 따라서 같은 프로그램이라면 사이클 시간이 짧을수록 빨리 끝날 것이다.

이제 앞에서 예로 든 어셈블리어 코드를 다시 살펴보자. 이 코드를 수행하는 시간, 즉 사이클 시간은 다음과 같이 구성된다.

사이클 시간 = 기계어 코드를 읽어 오는 시간 + 기계어 코드를 해석하는 시간 + 100번지에서 데이터를 읽어 오는 시간 + 산술 연산(ADD) 시간 + 레지스터에 저장하는 시간

사이클 시간을 줄이려면 이를 구성하는 다섯 가지 종류의 시간을 모두 줄이는 것이 좋을 것이다. 그런데 기계어 코드를 해석하는 시간이나 산술 연산 시간, 레지스터에 저장하는 시간에 비해, 메모리에서 기계어 코드와 데이터를 읽어 오는 시간이 상대적으로 매우 길다. 따라서 이 두 시간을 줄이는 것이 효과적이다.

미리보기

스트레치 컴퓨터 프로젝트가 시작되기 전부터 메모리 접근 시간을 줄이려는 노력이 있었다. 1954년에 진 암달은 미리보기lookahead라는 개념을 제안했고 존 배커스와 함께 이를 구체화했다.​8​ 암달이 제안한 미리보기란, 다음 기계어 코드를 미리 메모리에서 읽어오자는 것이다. 앞의 예에 나온 사이클을 보면, 기계어 코드를 읽어 와서 해석한 후, 데이터를 읽어 오게 되는데 데이터를 읽어서 처리하는 시간에 미리 다음 기계어 코드를 읽어올 수만 있다면 사이클 시간이 크게 감소할 것이다.

미리보기의 개념을 처음 적용한 컴퓨터가 스트레치 컴퓨터였다. 개념은 단순했지만 실제로 이를 현실화하는 작업은 쉽지 않았다. 왜냐하면 미리보기가 항상 완벽하게 동작하기는 어렵기 때문이었다. 예를 들어 분기가 발생하면 다음 기계어 코드의 주소가 완전히 달라지기 때문에 메모리 접근 주소에 해당하는 메모리 버스와 충돌할 수 있다. 이런 예외적인 경우들을 모두 고려하기란 쉽지 않았다. 존 코크는 시뮬레이터를 작성해서 미리보기 회로의 동작을 검증했다.

스트레치 컴퓨터 프로세서의 구조​8​

위의 그림은 스트레치 컴퓨터 프로세서의 내부 구조이다. 그림을 자세히 보면, LOOKAHEAD는 기계어 코드를 미리 가져오는 일만 하는 것이 아니라 데이터를 읽어 오는 일도 하고, 산술논리연산 회로의 결과값이 메모리에 저장되어야 할 때는 그 일도 처리한다. 스트레치에서 LOOKAHEAD는 현대의 캐시 메모리와 유사하다. LOOKAHEAD 내부에는 데이터를 버퍼링하기 위한 저장 공간이 존재하며 ‘인덱싱 회로’에서 요청하는 메모리 접근을 실제 메모리까지 접근하지 않고도 처리해 줄 수 있다. 현대 컴퓨터에서 사용되는 캐시 메모리가 아직 등장하기 전이었음에 유의하자.

메모리 인터리빙

미리보기 기능은 개념적으로 그럴듯하다. 그런데 문제가 있다. 만약 데이터를 읽어오는 작업과 기계어 코드를 읽어오는 작업이 같은 메모리에서 발생한다면 동시에 진행이 불가능할 것이다. 왜냐하면 메모리와 연결된 데이터 신호선이 한 벌인 경우에는 동시에 두 개의 값을 신호선에 실을 수 없기 때문이다. 스트레치에서는 이를 해결하기 위해 메모리를 쪼갰다. 물리적으로 둘로 나누는 것이 아니라, 주소공간에 따라 서로 다른 신호선이 사용되도록 분리하는 것이다. 예를 들면, 0번으로 끝나는 주소에 해당하는 메모리와 1번으로 끝나는 주소에 해당하는 메모리가 완전히 다른 물리적 신호선으로 프로세서와 연결되도록 한다. 그러면 0번으로 끝나는 주소에 저장된 데이터에 접근하는 동안, 1번으로 끝나는 주소에 저장된 기계어 코드를 읽어올 수 있다. 스트레치에서는 네 개의 블록으로 메모리를 쪼갰다. 위의 그림을 보면 MEMORY BUS CONTROL UNIT에 복수 개의 메모리가 연결됨을 알 수 있다.

파이프라이닝

스트레치의 주요 특징 중 하나가 파이프라이닝pipelining이다. 파이프라이닝이란 기계어 코드를 처리하는 사이클을 구성하는 단계들이 동시에 병렬적으로 진행하는 것을 말한다. 파이프라이닝이 가능하게 하려면 사이클을 구성하는 단계가 서로 독립적으로 동작할 수 있어야 한다. 스트레치에서부터 파이프라이닝이 가능해진 이유는, 미리보기 기능 때문이다. 미리보기가 가능하게 만들기 위해 기계어 코드를 읽어오는 단계와 메모리에 접근하는 단계가 독립적으로 동작하도록 구성했다. 이렇게 독립적이 되었으므로 이 두 개의 단계를 동시에 수행할 수 있게 된 것이다.

1 2 3 4 5 6 7