정보공간_1

[2기 수원 이동열] Instruction Set 본문

IT 놀이터/Elite Member Tech & Talk

[2기 수원 이동열] Instruction Set

알 수 없는 사용자 2012. 8. 23. 00:16

Instruction set과 관련 글을 남기게 된 수원 멤버십에 이동열이라고 합니다.

Instruction set이라 하면 실제로 컴퓨터가 수행하는 명령어의 집합을 의미하게 되는데, 일반적으로 프로그래머나 개발자가 프로그램으로 제작할 때는 high level language를 사용하게 됩니다. 이 범주에는 C언어가 속하게 됩니다. 컴퓨터의 입장에서는 high level language로 작성된 프로그램 코드들은 인식하지 못하며, 컴퓨터가 인식하는 instruction으로 변환을 시켜주어야 하는데, 이러한 과정을 컴파일이라고 정의를 합니다.

하지만 instruction이라고 하여도 모든 동일한 수행시간을 가지고 있지 않으며, 같은 instruction이라도 배치에 따라 수행시간이 다른데, 그 이유를 한번 살펴 보고자 합니다. 프로그램들은 컴퓨터가 인식할 수 있는 일련의 instruction들로 구성이 되어 있습니다. 프로그램을 실행한다는 것은 instruction들을 읽어 들여 처리하는 것을 말합니다. 컴퓨터 입장에서는 일련의 instruction들 중에 어떤 것을 실행시켜야 할지 결정을 해야 하는데, 이것을 알려주는 것을 일반적으로 PC(program counter)라는 레지스터입니다.

PC(program counter)값을 지정하는 레지스터는 컴퓨터 아키텍처마다 다르게 정의를 하고 있는데요. 일반적으로 데스크 탑에서 많이 사용하는 PC x86 계열의 컴퓨터에서는 EIP 레지스터에서 이러한 정보를 저장하고 있으며, 최근 널리 보급된 스마트 기기에 탑재되는 ARM 아키텍처에서는 r15 레지스터가 이러한 정보를 보관하고 있습니다.

Instruction set이 컴퓨터에서 실행되는 방법에 대해서 살펴보면, 파이프라이닝 방법을 활용하여 크게 5단계로 나누어 실행을 하도록 하고 있습니다. 이러한 방법을 ILP라 하여 instruction level parallelism이라고 합니다.



 

1. Instruction fetch

PC(program counter)가 가리키는 instruction memory로부터 instruction을 가져오고, 동시에 PC(program counter)를 증가시켜서 다음에 실행할 명령어를 가져올 수 있도록 하는 단계이다.

2. Instruction decode

Instruction operation을 구분하고, 실행에 필요한 register의 데이터를 추출하여 실행 시킬 수 있도록 하는 과정이다.

3. Execution

실제 ALU 연산을 실시하는 부분으로, instruction decode 단계에서 가져온 레지스터에 저장된 데이터를 기반으로 연산을 실시한다.

4. Memory read/write

Instruction memory와 관련된 operation이라면 data memory에 접근하여 연산 결과를 기록하거나 또는 메모리로부터 데이터를 가져오는 단계입니다.

5. Register write back

메모리에 데이터를 저장시키는 것이 아니라면, 가장 마지막에 레지스터에 저장시켜야 하는 instruction들이 존재하는데, insruction들은 이 단계에서 destination 레지스터에 연산 결과를 저장시키고, instruction을 완료하게 된다.

이렇게 5단계를 거쳐 명령어가 수행이 됩니다. 각 단계를 1 cycle이라고 하면 load 명령어의 경우 총 5 cycle이 소모가 되고 있으며, add와 같은 단순 ALU 연산 명령어의 경우 4 cycle 완료를 하게 됩니다. 이렇게 각 instruction들이 다르게 수행되기 때문에 실행되는 시간이 다르게 됩니다.

instruction set은 기본적인 실행시간뿐만 아니라 instruction 배치에 의해서도 실행 시간의 영향을 받게 되는데요. 그것들을 hazard라고 합니다.

 


첫 단계에서 branch 명령어가 실행이 된다고 가정을 해보면, branch 명령어가 instruction decode 단계에 이르게 되는 시점에, 다음 instruction이 파이프라인 안으로 들어오게 됩니다. 하지만 이렇게 fetch instruction의 경우 실제로 instruction을 수행할지 여부는 branch발생여부에 따라 결정됩니다. 실제 Branch의 발생 여부를 알게 되는 시점은 ALU 연산 이후이기 때문에, 만약 branch가 발생을 하게 된다면 파이프라인에 들어온 명령어를 비워야 하는데, 이것을 버블이라고 합니다.

그리고 이렇게 branch 명령어에 발생되는 hazard control hazard 또는 branch hazard라고 부릅니다.

만약 명령어 배치를 수정하여, 배치시키면 bubble이 발생하지 않아도 원하던 대로 소스코드가 동작할 수 있도록 작성을 할 수 있습니다.



코드 배치가 약간만 수정이 되어도 버블이 발생하지 않고, 파이프라인이 쉬지 않고 계속 명령어들을 실행 시킬 수 있다는 것을 확인할 수 있습니다.

컴퓨터 하드웨어에서 branch 명령어를 실행을 시킬 때, 바로 아래에 있는 instruction fetch 하도록 하는 파이프라인 구현방법을 delayed branch라고 합니다. 하지만 delayed branch에서 중요한 건 branch 명령어에 영향을 받지 않고도 실행되어야 할 명령어를 찾아서 삽입을 시켜줘야 합니다. 이러한 일들은 일반적으로 컴파일러에 의해서 처리되고 있습니다.

 

 

위의 코드를 보면 load 명령어의 destination 레지스터 값이 다음 명령어의 source 레지스터로 활용이 되고 있습니다. Load 명령어의 경우는 파이프라인 구현상 가장 마지막 단계에 도달하여야 destination 레지스터에 저장할 값을 알 수 있게 됩니다. 따라서 이렇게 load 명령어의 destination이 다음 명령어의 source 레지스터로 사용되는 경우도 bubble이 발생되는데, 이러한 hazard data hazard라고 부릅니다.

하지만 이렇게 작성된 코드도 instruction의 배치에 따라서 더 효율적인 코드로 수정이 가능합니다.



Data hazard가 발생하는 원인은 load 명령어와 다음 명령어가 dependency가 있기 때문에 발생하는 것인데, 만약에 load 명령어 다음에 dependency가 없는 명령어를 배치를 하게 된다면, 버블을 일으키지 않고도 계속 명령어를 수행할 수가 있게 됩니다.

일반적으로 프로그램의 개발은 high level language에서 많은 작업이 이뤄지며, 작성된 프로그램은 컴파일러와 assembler를 거쳐서 컴퓨터 CPU가 인식할 수 있는 binary들로 번역이 됩니다. binary들을 실행하는 CPU입장에서는 각 instruction마다 실행하는데 걸리는 시간이 다를뿐더러, control hazard data hazard등에 따라서 전체적인 프로그램의 실행 속도가 좌우가 됩니다.

특히 control hazard data hazard에 의해 발생하는 문제점들은 instruction의 배치에 따라 성능향상이 가능하기 때문에 관련된 이슈를 컴파일러와 하드웨어와 관련된 연구를 통해서 극복하려는 사례가 많이 있습니다. Gcc compiler optimization 기능이 좋은 예라고 할 수 있습니다. 하지만 컴파일러가 최적화를 시켜주는 것도 한계가 존재하기 때문에, 더 효과적인 방법은 실제로 컴퓨터 프로그래머나 개발자가 코드를 최적화 시켜서 제작하는 것이 좋은 방법 중에 하나입니다.

Instruction의 실행은 실제 컴퓨터 동작에서 많은 영향을 미치게 됩니다. 따라서 최신 컴퓨터내에 탑재되어 있는 하드웨어에서는 여러 가지 기능들을 구현하고 더 많이 복잡해지고 있습니다본 포스팅에서는 5단계 파이프라인 방법을 다루었지만, 최신 컴퓨터들은 8단계이상의 파이프라인들로 구성이 되며, 또한 super scalar, out of execution instruction 실행에 여러 가지 알고리즘을 CPU에 적용을 시키고 있습니다.