명령어 집합 구조 (ISA)

Jin Hur·2021년 8월 17일
0

reference: "프로그래머가 몰랐던 멀티코어 CPU 이야기" / 김민장, "Computer System A Programmers'Perspective" / 랜달 E.브라이언트

source: http://melonicedlatte.com/computerarchitecture/2019/01/30/192433.html

프로세서가 이해하는 언어, 명령어 집합 구조(Instruction Set Architecture). ISA는 프로그래머와 프로세서가 직접적으로 소통할 수 있는 언어이다.

인텔 계열 프로세서와 AMD 계열 프로세서는 내부 구조가 사뭇 다르지만 같은 언어, 즉 같은 ISA(x86)를 사용하기에 별 문제 없이 프로그램이 작동.
반면 ISA가 다른 프로세서라면 x86 ISA로 만들어진 프로그램은 바로 작동할 수 없음.

  • 프로세서가 이해하는 명령어 하나를 Instruction이라 하고, 고급 언어는 컴파일러나 인터프리터를 거쳐 프로세서가 이해할 수 있는 원시적인 Instruction의 흐름으로 바뀜. 수 많은 명령어들이 서로 맞물려 거대한 프로그램을 만드는 것과 같다. 마치 수 많은 벽돌들이 맞물려 거대한 건물을 만들듯이.
  • 명령어는 성격에 따라 여러 종류로 나눌 수 있으며, 크게 (1) 기본적인 사칙연산과 논리연산, (2) 메모리에 읽고(load), 쓰는(store) 명령, (3) 프로그램의 실행 흐름을 제어하는 분기 및 호출 명령(if/goto, call/return)으로 구분할 수 있다. (이 외에도 시스템 내부를 제어하거나 상태를 관찰하는 명령도 있음)
  • 하나의 명령어에는 이 명령어의 종류와 피연산자 등 여러 가지 내용이 기술된다. 예를 들어 "add dword ptr A, 0x07 : 4바이트 정수 변수 A에 상수 7을 더해 다시 A에 씀."
  • 이 명령어는 정수 형태로 전달되는데, 이진수 값으로 바꾸는 것을 인코딩, 그 반대를 디코딩이라 함.

    추가로 명령어에는 부사를 덧붙이는 것과 같이 세밀한 동작을 추가할 수 있다. 예를 들어 LOCK 접두어를 추가하여 원자성을 보존하도록 하는 것이다.(= 멀티스레드 실행에서 안전할 수 있도록)


RISC와 CISC 명령어 집합 구조

초기 범용 목적 마이크로프로세서에선 컴파일러의 도움이 크지 못하였음. 따라서 풍부한 여러 명령어를 제공하려 노력했으며, 메모리도 매우 비쌌던 시기라 조금이라도 명령어 크기를 줄여 짧은 길이의 명령어에 많은 뜻을 함축하도록 설계해야했다. 이러한 이유로 최초의 ISA는 복잡한 형태의 CISC(Complex Instruction Set Computer)의 형태를 띄었다. 대표적인 예로 인텔의 x86 명령어 구조.

x86

x86 또는 80x86은 인텔이 개발한 마이크로프로세서 계열을 부르는 말이자, 이들과 호환되는 프로세서들에서 사용한 명령어 집합 구조들을 통칭하는 말이다.

x86 명령어 구조

x86 ISA는 1986에 출시된 인텔 80386의 명령어 집합 구조로 정확한 명칭은 IA-32이다. 그리고 AMD가 제안한 x86의 64비트 확장(AMD64, x86-64)도 이제는 사실상 표준(인텔에서는 이를 intel64라 부름).

CISC의 주요 특징

  • 명령어 길이가 주로 가변적, 여러 복잡한 형태의 주소 모드의 지원
  • 범용 레지스터의 개수가 비교적 적음.

RISC 등장

태초의 ISA는 CISC 형태를 띠었으나 실제 사용되는 명령어 종류가 그렇게 많지 않다는 것을 발견. 컴파일러가 CISC 명령어 집합이 지원하는 모든 명령어와 주소 모드를 사용하지 않고 일부만 자주 사용.
또한 13비트 정도면 충분한 상수들을 표현.

"Make Common Case Fast"

자주 쓰이지 않는 명령어 종류도 줄이고 상수를 표현하는데 32비트를 모두 쓸 필요도 없다는 결론 도달.
프로세서 구조의 단순함과 고속화를 위해 명령어 길이를 4바이트 정도로 제한해도 큰 문제가 없을 것.
명령어 크기를 고정하고 그 갯수를 대폭 줄인 RISC 형식의 명령어 구조 탄생.
ARM은 대표적인 RISC 프로세서.

간단한 명령어는 그만큼 회로가 단순해져서 더 저렴한 비용을 제조할 수 있다. 대신 소프트웨어인 컴파일러가 일을 더 하게 됨.
반면 x86과 같은 CISC는 옵코드의 위치와 길이조차 가변이어서 분석 작업이 훨씬 복잡하고, 이는 필연적으로 하드웨어 회로의 복잡합으로 이어짐.

CISC와 RISC의 가장 큰 차이

주소 모드의 단순함과 복잡함도 두 명령어 구조의 뚜렷한 특징. 주소 모드란 명령어 속에 포함된 피연산자를 어떻게 해석할지를 결정. 가장 큰 차이는 CISC는 피연산자로 메모리 주소가 바로 올 수 있지만, RISC는 레지스터만 올 수 있다.

CISC로도 얼마든지 RISC 형식으로 코드를 만들어낼 수 있음.
또한 CISC 명령에 바로 메모리 주소가 온다 하더라도 프로세서가 직접 메모리 주소를 대상으로 연산을 하는 것이 아님.

결국 CISC는 컴퓨터 프로그램의 복잡함을 하드웨어가 도맡아 처리하고, RISC는 하드웨어의 복잡함을 일부 소프트웨어, 즉 컴파일러로 넘긴 셈이다.
하드웨어 복잡함을 덜어서 절약한 트랜지스터 수를 RISC는 성능 향상에 투자할 수 있었음. 대표적으로 RISC의 범용 목적 레지스터 개수는 CISC보다 많다.

아직도 CISC vs RISC?

세상이 바뀌어 이제는 위와 같은 단순 비교가 성립하지 않음. 팬티엄 프로부터 x86 프로세서는 CISC 명령어를 단순한 RISC처럼 생긴 내부 마이크로 명령어, 또는 마이크로 옵(micro-op, uop)로 쪼개어 처리함. 이는 x86 코드가 표현은 함축적이고 복잡하지만, 실제 프로세서 내부로 들어가면 마치 ARM 코드(RISC)처럼 여러 개로 나뉜다는 뜻이다. 겉은 CISC 형태를 띄지만 내부는 RISC처럼 작동한다. (물론 복잡한 CISC 명령어를 해독하는 로직이 필요하지만, RISC의 장점을 고스란히 x86 프로세서에도 사용할 수 있게 되었음.)

x86의 범용 레지스터 개수는 프로그래머와 약속한대로 8개, x86-64의 레지스터는 16개밖에 없다. 그러나 실제 물리적인 레지스터의 개수는 보통 100개 이상으로 이 보다 훨씬 많다. 이 레지스터들을 '레지스터 리네이밍'이란 테크닉으로 사용된다. 이처럼 도식적인 CISC RISC의 구분은 더 이상 무의미해졌다.

하지만 CISC의 잔재는 x86 프로세서 여러 곳에서 찾아볼 수 있다. x86 ISA의 특징 중 하나인 적은 수의 범용 레지스터를 고려해야한다. 하드웨어 내부에는 많은 물리 레지스터가 있지만, 컴파일러는 여전히 8개 혹은 16개의 범용 레지스터만 있는 것으로 가정하고 코드를 만들어야 한다. 이는 하위 호환성을 위해서이다. 그 결과 레지스터 부족을 매꾸기 위해 x86 코드는 많은 수의 메모리 접근 명령어가 생성된다. 임시 계산 결과 값을 담을 레지스터가 부족하므로 메모리에 잠시 저장했다가 다시 불러와야 하기 때문이다(register spill). 큰 캐시로 어느 정도 극복되지만 많은 수의 메모리 입출력 명령어들은 성능 저하의 주요 원인이다.

x86 같은 가변 길이 명령어를 해독하려면 반드시 복잡한 회로가 필요하다. 실제 x86 프로세서의 디코더(명령어를 해독하는 장치)는 상당히 복잡하다. 하지만 이는 트랜지스터 개수가 기하급수적으로 증가한 현재에서는 그다지 심각한 문제가 되지 않다.

정리

프로세서 내부를 이해하려면 가장 먼저 프로세서가 쓰는 언어인 ISA를 이해해야함.
프로세서를 만든다 할 때도 가장 먼저 ISA부터 설계해야 함.
CISC, RISC ISA가 있지만 현재에는 이 경계가 모호해졌다.

0개의 댓글