반응형

Python 공식문서에 따르면 super 클래스의 역할은 아래와 같음

Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class.

공식문서 설명은 늘 어려움.

쉽게 말해, 부모나 형제 클래스의 임시 객체를 반환하고, 반환된 객체를 이용해 슈퍼 클래스의 메소드를 사용할 수 있음.

즉, super() 를 통해 super class의 메소드에 접근 가능

단일상속에서 super()

 
class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

 

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

square = Square(4)
square.area() # 16
  • Rectangle 클래스를 상속받기 때문에 Rectangle의 area() 메소드 사용 가능

 

super() with parameters


  • super() 는 2가지 파라미터를 가질 수 있음
    • 첫번째 : subclass
    • 두번째 : subclass의 인스턴스 객체
class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

class Square(Rectangle):
    def __init__(self, length):
        super(Square, self).__init__(length, length)
  • 단일 상속인 경우에는 super(Square, self)와 super()는 같은 의미

아래의 경우는?

class Cube(Square):
    def surface_area(self):
        face_area = super(Square, self).area()
        return face_area * 6

super(Square, self).area()

첫번째 argument : subclass 인 Square

  • Cube가 아닌 Square기 때문에 super(Square, self)의 반환은 Square 클래스의 부모 클래스인 Rectangle 클래스의 임시 객체
  • 결과적으로 Rectangle 인스턴스에서 area() 메소드를 찾음

Q. Square 클래스에 area 메소드를 구현하면??

  • 그래도 super(Square, self) 가 Rectangle 클래스를 반환하기 때문에 Rectangle 인스턴스에서 area() 메소드를 호출
## super 클래스의 정의
class super(object):
	def __init__(self, type1=None, type2=None): # known special case of super.__init__
	        """
	        super() -> same as super(__class__, <first argument>)
	        super(type) -> unbound super object
	        **super(type, obj) -> bound super object; requires isinstance(obj, type)
	        super(type, type2) -> bound super object; requires issubclass(type2, type)**
	        Typical use to call a cooperative superclass method:
	        class C(B):
	            def meth(self, arg):
	                super().meth(arg)
	        This works for class methods too:
	        class C(B):
	            @classmethod
	            def cmeth(cls, arg):
	                super().cmeth(arg)
					"""
	        
	        # (copied from class doc)

두번째 argument : 첫번째 argument의 클래스 인스턴스를 넣어주거나 subclass를 넣어줘야함

print(issubclass(Cube, Square)) # True
반응형
반응형

이 글은  컴퓨터 밑바닥의 비밀 chapter 5.2의 내용을 읽고 요약한 글입니다. 

5.2.1 프로그램 지역성의 원칙

프로그램 지역성의 원칙(locality of reference or principle of locality)

  • 본질은 매우 규칙적으로 메모리에 접근한다는 것으로 크게 2가지 종류가 있음
 

 

(1) 시간적 지역성 (temporal locality)

  • 프로그램이 메모리 조각에 접근하고 나서 이 조각을 여러번 참조하는 경우를 이야기 함
  • 캐시 친화성이 매우 높은데, 데이터가 캐시에 있는한 메모리에 접근하지 않아도 반복적으로 캐시의 적중이 가능하다는 단순한 이유

 

(2) 공간적 지역성(spatial locality)

  • 캐시가 적중하지 않으면 메모리의 데이터를 캐시에 적재해야 함
  • 일반적으로 요청한 메모리의 인접 데이터도 함께 캐시에 저장되므로 프로그램이 인접 데이터에 접근할 때 캐시가 적중하게 됨

 

5.2.2 메모리 풀 사용

  • 메모리를 동적으로 할당받을 때 일반적으로 malloc을 이용
  • 메모리 조각 N개를 할당받을 때 malloc을 사용하면 N개 조각이 힙 영역의 이곳 저곳에 흩어져 공간적 지역성에 좋지 않음
  • 메모리 풀 기술은 커다란 메모리 조각(연속적인 메모리 공간)을 미리 할당받으며 메모리 요청/해제 할 때 더 이상 malloc을 거치지 않아 캐시 친화적

 

5.2.3 struct 구조체 재배치

Linked list에 특정 조건을 만족하는 노드가 있는지 판단하려고 할 때, 구조체는 다음과 같이 정의

#define SIZE 100000

struct List
{
	List* next;
	int arr[SIZE];
	int value;
}

Linked list의 노드에는 필요한 값 value와 다음 노드를 가리키는 next 포인터 외에도 배열 arr이 포함되어있음

bool find(struct List* list, int target)
{
	while(list)
	{
		if(list->value == target)
			return true;
		
		list = list->next;
	}
	
	return false;
}
  • 위 코드에서 빈번하게 사용되는 항목은 next 포인터와 value값이며, 배열 arr은 전혀 사용되지 않음
  • 하지만 next 포인터와 value 값이 배열 arr에 의해 멀리 떨어져 있기 때문에 공간적 지역성이 나빠질 수 있어 아래 처럼 함께 배치 → 캐시 적중률 상승시킬 수 있음. 
#define SIZE 100000

struct List
{
	List* next;
	int value; // next 아래에 value
	int arr[SIZE];
}

 

5.2.4 핫 데이터와 콜드 데이터의 분리

  • 핫데이터 (hot data): 더 빈번하게 사용되는 데이터
  • 콜드 데이터(cold data): 덜 빈번하게 사용되는 데이터

위 코드에서 next, value는 핫데이터, arr는 콜드 데이터라고 할 수 있음

일반적으로 Linked list에 노드가 하나뿐인 경우는 거의 없으며, 노드가 비교적 많을 때에는 캐시해야 할 노드가 비교적 많아짐 → 캐시 용량은 제한적!

Linked list 자체가 차지하는 저장 공간이 클수록 캐시에 저장할 수 있는 노드는 줄어듬. 아래와 같이 배열 arr을 다른 구조체에 넣고 List 구조체 안에 이 구조체를 가리키는 포인터를 추가할 수 있음

#define SIZE 100000

struct List
{
	List* next;
	int value; // next 아래에 value
	strcut Arr* arr;
};

struct Arr
{
	int arr[SIZE];
};
  • 콜드 데이터와 핫 데이터를 분리하면 더 나은 지역성을 얻을 수 있는데 이런 방법을 사용하려면 각 항목하다 접근 빈도를 알고 있어야 함

 

5.2.5 캐시 친화적인 데이터 구조

  • 지역성 원칙 관점에서 배열이 linked list보다 나음
    • C++, std::vector 컨테이너가 std::list 컨테이너 사용보다 나음
  • 실제로 사용할 때는 캐시 친화적 여부를 포함하여 구체적인 상황에 맞추어 선택해야 함
  • 예를 들어, 배열의 공간적 지역성은 linked list보다 낫지만, 노드의 추가/삭제가 빈번하게 발생하는 경우에는 linked list가 배열에 비해 우수함
    • Linked list의 노드 추가/삭제에 대한 시간 복잡도 O(1)
  • 노드의 추가/삭제가 쉬운 장점을 유지하면서 캐시 친화적이고 싶은 linked list를 사용하려면? → 직접 정의한 메모리 풀에서 메모리를 요청하면 됨

이런 최적화를 진행할 때 반드시 분석 도구를 사용하여 캐시의 적중률이 시스템 성능에 병목이 되는지 판단해야 함. 병목이 되지 않으면 굳이 이런 최적화를 할 필요가 없음

 

5.2.6 다차원 배열 순회

  • 배열을 row, column 순서로 순회하면서 값을 모두 더하는데 C언어 역시 row 우선 방식(row major)으로 배열을 저장
    • row major: 메모리에 저장할 때 첫번째 행이 첫번째 열부터 마지막 열까지 저장되고, 이어서 두번째 행이 첫번째 열부터 마지막 열까지 저장되는 것을 반복하는 구조
int matrix_summer(int A[M][N])
{
    int i, j, sum = 0;
	
    for(i=0; i< M;i++)
    {
        for(j=0;j<N;j++)
        {
            sum += A[i][j];
        }
    }
	
    return sum;
}

 

  • 배열에 행 4개와 열 8개가 있다고 가정 (M이 4, N이 8)
  • 캐시가 최대 4개의 int 형식 데이터를 저장할 수 있다고 가정

  • 순회가 시작되면 캐시에 아직 배열에 대한 데이터가 없기 때문에 캐시는 비어있어 배열 A의 첫번째 요소인 A0에 접근하는 시점에 캐시가 적중할 수 없음 → A0를 포함한 요소 4개가 캐시에 저장됨

 

  • 캐시 준비가 완료되어 A1부터 A3에 접근할 때는 모두 캐시가 적중하기 때문에 메모리에 접근할 필요가 없음

 

  • A4에 접근할 때는 캐시 용량의 제한으로 캐시가 적중할 수 없는데 다시 A4개를 포함한 요소 4개가 캐시에 저장되어 이전 데이터와 교체됨

  • 각 행마다 8개 요소들 중 2개를 miss하기 때문에 75% 의 캐시 적중률을 보여줌

 

이번에는 열 우선 방식으로 접근 (column major)

int matrix_summer(int A[M][N])
{
	int i, j, sum = 0;
	
	for(j=0; j< N;j++)
	{
		for(i=0;i<M;i++)
		{
			sum += A[i][j];
		}
	}
	
	return sum;
}

 

첫번째 요소인 A0에 접근하면 아직 캐시가 적중할 수 없기 때문에 아래와 같이 A0부터 A3까지 캐시에 저장됨

그러나 우리 코드에서 다음에 접근할 요소는 A8이며 이 데이터는 여전히 캐시가 적중할 수 없고 캐시에 A8부터 A11을 저장. 이전 캐시는 사용되지 못함

  • 위와 같이 열 우선 방식으로 배열을 순회하면 캐시는 매번 적중에 실패하여 적중률은 0

 

반응형
반응형

이 글은  컴퓨터 밑바닥의 비밀 chapter 5.1의 내용을 읽고 요약한 글입니다. 

5.1.1 CPU와 메모리의 속도 차이

https://softwareg.com.au/blogs/computer-hardware/cpu-bandwidth-vs-memory-bandwidth

  • CPU 속도는 1년에 52%씩 성능이 증가한 반면, 메모리 속도는 1년에 7%씩 증가
  • CPU는 명령어를 실행할 때 어쩔 수 없이 메모리의 처리를 기다려야 함
    • 일반적인 시스템에서 메모리의 속도는 CPU의 100분의 1

 

5.1.2 도서관, 책상, 캐시

  • 도서관에서 책상은 캐시에 비유할 수 있고, 서가는 메모리에 비유 가능
  • CPU와 메모리 사이에 캐시 계층이 추가되어, 최근에 메모리에서 얻는 데이터가 저장됨
  • 캐시가 적중하면 메모리에 접근할 필요가 없어 CPU가 명령어 실행하는 속도를 크게 끌어올릴 수 있음
    • 캐시 적중: CPU가 요청한 데이터가 캐시에 이미 저장되어있어 캐시가 해당 요청을 이행할 수 있을 때 발생

CPU는 메모리와 직접 상호 작용하지 않고 캐시와 상호 작용

 

 

  • 일반적으로 x86 CPU와 메모리 사이에는 세 단계의 캐시가 추가 되어 있음
    • L1, L2, L3
  • L1 캐시 접근 속도: 레지스터 접근 속도에 비해 약간 느리지만 대동 소이하기 때문에 대략 4클럭 주기가 소요됨
  • L2캐시 접근 속도: 대략 10클럭 주기 소요
  • L3캐시 접근 속도 : 대략 50클럭 주기 소요
  • 캐시 단계에 따라 접근 속도는 낮아지지만 용량을 증가

  • L1, L2, L3 캐시, CPU 코어는 레지스터 칩 내에 묶여 패키징되어 있음
  • CPU가 메모리 내에 데이터가 필요하다고 판단하면 우선 L1 캐시에서 데이터를 검색
  • 없다면 L2, L3 를 순차적으로 검색 후 모두 없으면 메모리에 직접 접근하여 캐시에 데이터를 갱신

 

5.1.3 공짜 점심은 없다: 캐시 갱신

불일치 문제

  • 캐시의 데이터는 갱신되었지만 메모리의 데이터는 아직 예전 것이 남아 있어 캐시와 메모리 사이에 불일치가 발생할 수 있음

해결 방법

1. 연속 기입(write-through)

  • 캐시를 갱신할 때 메모리도 함께 갱신하는 방법
  • 캐시를 업데이트하면 어쩔 수 없이 메모리에 접근해야 함 → CPU는 메모리가 갱신될 때까지 대기하고 있어야 함 → 동기식 설계 방법
  • 이 상황을 최적화하는 방법은 동기를 비동기로 바꾸는 것

2. 후기입(write-back)

  • 캐시 용량에 한계가 있어 용량이 부족하면 자주 사용되지 않는 데이터를 제거해야 함
  • 캐시에서 제거된 데이터가 수정된 적이 있다면 이를 메모리에 갱신해야 함
  • 캐시의 갱신과 메모리의 갱신이 분리되므로 비동기임
  • 연속 기입보다 훨씬 복잡하지만 성능은 분명 더 나음

자주 사용할만한 데이터를 캐싱하는데, 삭제할 때만 메모리에 갱신하는 방식이 후기입

 

5.1.4 세상에 공짜 저녁은 없다: 다중 코어 캐시의 일관성

  • 단일 CPU 성능 향상이 어려워 숫자를 늘리게 됨 → 다중 코어 시대 진입
  • CPU가 코어를 여러개 가지면 다른 문제가 발생하는데 두 코어에서는 다른 스레드 2개가 실행됨

Step 1

  • 두 스레드 모두 메모리 내 X변수에 접근해야하고 변수의 초기값이 2라고 가정
  • 코어 2개는 모두 X 변수를 사용해야 한다

캐시 동작 원리에 따르면,

  • 처음으로 X 변수를 읽으면 캐시에 적중할 수 없으므로 X 변수를 메모리에서 읽어야 함
  • 이어서 대응하는 캐시에 갱신되므로, 이제 cpu1 캐시와 cpu2 캐시는 모두 X 변수를 가지고 있고 그 값은 2

 

Step2

  1. C1은 X 변수에 2를 더해야 하는데 캐시 동작 원리에 따라 C1은 캐시에서 X 변수 값을 가져온 후 2를 더해서 다시 캐시에 갱신
  2. 이 시스템이 연속 기입(write-through) 방식이라고 가정하면 메모리도 동기 방식으로 동시에 갱신되고 메모리에 있는 X 변수 값은 4

 

Step3

  1. cpu2 도 X 변수에 덧세 연산을 수행하는데 4를 더한다고 가정
    1. 캐시 동작 원리에 따라 cpu2는 캐시에서 X 변수 값을 가져온 후 4를 더해서 다시 캐시를 갱신
    2. 캐시 값은 6으로 바뀌는데 연속 기입 방식에 따라 메모리를 갱신하면 메모리에 저장된 값도 6이 됨 ⇒ 문제 바생
  • 초기 값이 2인 변수에 2 와 4를 각각 더한 후 결과는 8이 되어야 하지만 X 변수 값은 6이 됨

문제는 메모리의 X 변수에 cpu1, cpu2의 캐시에 복사본 2개를 가지고 있기 때무네 발생하고 C1 캐시가 갱신될 때 C2 캐시는 동기적으로 수정되지 않아 다중 코어에서 캐시 데이터의 불일치 문제가 발생함

⇒ 해결책: 캐시 한개에 갱신된 변수가 다른 CPU 코어의 캐시에도 존재한다면 함께 갱신되어야 함

  • 최신 CPU에는 고전적인 MESI 규칙(MESI protocol)같은 다중 코어 캐시의 일관성을 유지하는 규칙이 있음

 

5.1.5 메모리를 디스크의 캐시로 활용하기

  • 디스크는 탐색(seek) 을 위해 10ms 가량의 시간이 소요됨 (메모리 접근 속도가 10만배 가량 빠름)
    • 물론 모든 디스크 접근에 탐색이 필요하지는 않음

메모리와 디스크의 속도 차이를 해결하기 위해 둘 사이에 직접 캐시를 추가하면 어떨까?

먼저 레지스터가 메모리의 캐시가 될 수 없는 이유는 레지스터 크기가 한정되어있기 때문. 메모리 용량은 사실 꽤 큰편이라(GB 단위) 어려움

  • 최신 운영체제는 분명히 메모리를 디스크의 캐시로 사용
  • 일반적으로 메모리 사용률은 100%에 도달하지 않으며 이 여유 공간을 낭비시키지 않도록 디스크의 캐시로 활용하여 디스크에서 데이터 읽어 오는 일을 최소화 함 → 페이지 캐시의 기본원리

결국 CPU의 내부 케시가 메모리 데이터를 저장하고, 메모리가 디스크 데이터를 저장

  • 최근 서버에서는 메모리가 디스크를 대체하는 것이 대세 → 메모리 가격이 엄청 저렴해졌기 때문
  • AWS에서는 2TB 용량의 메모리를 탑재한 인스턴스도 제공
  • Presto, Flink, Spark 같은 메모리 기반 시스템이 디스크 기반의 경쟁자들을 빠르게 대체하고 있음

 

5.1.6 가상 메모리와 디스크

  • 앞서 모든 프로세스가 표준 크기의 자체적인 주소 공간을 가지고 있다고 여러번 이야기 함
    • 물리 메모리와는 관련이 없어 물리 메모리의 크기를 초과활 수 있음

Q. 시스템에 프로세스 N개가 실제 물리 메모리를 모두 사용하고 있을 때 새로운 프로세스가 생성되어 N+1번째 프로세스도 메모리를 요청한다면 시스템이 어떻게 처리할까?

A. 디스크는 메모리의 창고 역할을 할 수 있어 자주 사용하지 않은 메모리 데이터를 디스크에 기록하고 데이터가 차지하던 물리 메모리 공간을 해제할 수 있음. 그러면 N+1 번째 프로세스가 다시 메모리를 요청할 수 있음

 

5.1.7 CPU는 어떻게 메모리를 읽을까?

  • CPU가 볼 수 있는 것은 모두 가상 메모리 주소로 실제 물리 메모리 주소로 변환되어야 함
  • 변환이 완료되면 캐시를 검색하기 시작하고 캐시에서 찾을 수 없을 때는 메모리에 접근
  • 프로세스의 데이터는 디스크에 임시로 보관되어 있을 수 있는데 이때 메모리에서 데이터를 찾을 수 없을 가능성이 있으며 디스크의 프로세스 데이터를 다시 메모리에 적재한 후 메모리를 읽어야 함
  • 빅 데이터의 시대가 도래해 단일 장치의 디스크만으로 더이상 데이터를 완전히 저장할 수 없다면 대안은?

 

5.1.8 분산 저장 지원

  • 분산 파일 시스템(distributed file system)을 직접 장착(mount)할 수도 있고, 로컬 디스크는 원격의 분산 파일 시스템에서 전송된 파일을 저장 → 로컬 디스크를 원격의 분산 파일 시스템의 캐시로 간주할 수 있음
  • 물론 응답 속도를 높이기 위해 원격 분산 파일 시스템의 데이터를 데이터 흐름(data stream) 형태로 직접 로컬 컴퓨터 시스템의 메모리로 끌어올 수도 있음 (apache kafka)
  • 이 경우 대용량 메시지는 원격 분산 파일 시스템에 저장되어 있지만 실시간으로 해당 데이터는 소비자에게 전달되고 이때 메모리를 원격 분산 파일 시스템의 캐시로 간주
반응형
반응형
 

이 글은  컴퓨터 밑바닥의 비밀 chapter 4.7 의 내용을 읽고 요약한 글입니다. 

4.7.1 복잡함을 단순함으로

  • 마이크로코드의 설계 아이디어: 복잡한 기계 명령어를 CPU 내부에서 비교적 간단한 기계 명령어로 변환하는것으로 컴파일러는 이 프로세스를 알지 못함
  • 마이크로코드에 버그가 있으면 컴파일러는 이를 피하기 위해 할 수 있는 일이 없음

또한 복잡한 기계명령어 하나가 같은 일을 하는 간단한 명령어 여러개보다 느리게 실행된다는 사실을 발견

4.7.2 축소 명령어 집합의 철학

  • 복잡 명령어 집합(Complex Instruction Set Computer, CISC) 에 대한 반성으로 축소 명령어(Reduced instruction Set Computer, RISC) 집합의 철학이 탄생

3가지 측면을 주로 반영

1. 명령어 자체의 복잡성

  • 복잡한 명령어를 제거하고 간단한 명령어 여러개로 대체
  • 마이크로코드 설계가 필요없으며 컴파일러에서 생성된 기계 명령어의 CPU 제어 능력이 크게 향상됨
  • 명령어 집합을 줄인다는 아이디어는 명령어 집합의 명령어 개수를 줄이다는 의미가 아니라, 하나의 명령어당 들여야 하는 연산이 더 간단하다는 의미

2. 컴파일러

  • 컴파일러가 CPU에 대해 더 강력한 제어권을 갖음
  • CPU는 더 많은 세부사항을 컴파일러에 제공함

3. LOAD/STORE 구조

  • 복잡한 명령어 집합에서는 기계 명령어 하나로 메모리에서 데이터를 가져오고(IF), 작업을 수행하고(EX), 해당 데이터를 메모리에 다시 쓰는 (WB) 작업을 모두 할 수 있음
  • 축소 명령어 집합의 명령어는 레지스터 내 데이터만 처리할 수 있으며, 메모리 내 데이터는 직접 처리할 수 없음 

Q. 누가 메모리를 읽고 쓰는 것인가?

  • LOAD와 STORE라는 전용 기계 명령어가 메모리의 읽고 쓰기를 책임짐
  • 다른 명령어는 CPU 내부의 레지스터만 처리

 

4.7.3 CISC, RISC 차이

두 숫자가 각각 메모리 주소 A와 주소 B에 저장된 상태에서 두 숫자를 곱한 값을 먼저 계산한 후 결과를 다시 메모리 주소 A에 기록 한다고 가정

(1) CISC의 경우

  • CISC는 적은 수의 기계 명령어로 가능한 한 많은 작업을 수행하는 것
  • MULT(iplication)이라는 기계 명령어가 있을 수 있으며 명령어를 실행할 때 다음 동작이 수행되어야 함
  1. 메모리 주소 A의 데이터를 읽어 레지스터에 저장
  2. 메모리 주소 B의 데이터를 읽어 레지스터에 저장
  3. ALU가 레지스터 값을 이용하여 곱셈 연산 수행
  4. 곱셈 결과를 다시 메모리 주소 A에 씀
MULT A B

a = a * b; // 고급 언어와 매우 유사. 이런 설계는 고급 언어와 기계 명령어 사이의 차이를 줄임

 

(2) RISC의 경우

  1. 메모리 주소 A의 데이터를 읽어 레지스터에 저장
  2. B의 데이터를 읽어 레지스터에 저장
  3. ALU가 레지스터 값을 이용하여 곱셈 연산 수행
  4. 곱셈 결과를 다시 메모리에 씀
  • RISC에서는 LOAD, PROD, STORE 명령어를 사용하여 작업을 단계별로 완료해야 함
    • LOAD: 메모리에서 레지스터로 데이터를 적재
    • PROD: 두 레지스터에 저장된 숫자의 곱셈 연산을 수행
    • STORE: 레지스터의 데이터를 다시 메모리에 씀

어셈블리어 코드

LOAD RA, A
LOAD RB, B
PROD RA, RB
STORE A, RA
  • 복잡 명령어 집합을 사용하는 프로그램보다 기계 명령어를 더 많이 사용
  • RISC의 의도는 프로그래머가 직접 어셈블리어로 코드를 작성하는 것이 아니라, 이 작업을 컴파일러에게 맡기고 컴파일러가 구체적인 기계 명령어를 자동으로 생성하게 하는 것

4.7.4 명령어 파이프라인

  • 위 어셈블리어 코드는 각 명령어가 매우 간단하여 실행 시간이 모두 거의 동일하여 파이프라인 기술을 통해 실행 효율을 높일 수 있음

4.7.5 천하에 명성을 떨치다

  • 1980년대 중반 RISC 기반의 CPU가 CISC 기반 CPU를 압도
반응형
반응형

이 글은  컴퓨터 밑바닥의 비밀 chapter 4.6 의 내용을 읽고 요약한 글입니다. 

 

4.6.1 프로그래머의 눈에 보이는 CPU

  • CPU는 단순하게 메모리에서 명령어를 읽어 실행하기만 하면 됨
  • 어떤 프로그램이든 컴파일러에 의해 기계 명령어로 변환될 뿐

4.6.2 CPU의 능력 범위: 명령어 집합

  • 명령어 집합은 우리에게 CPU가 할 수 있는 일을 알려줌
  • 서로 다른 형태의 CPU는 다른 유형의 명령어 집합을 가지고 있음

4.6.3 추상화: 적을수록 좋다

  • 1970년까지 컴파일러가 성숙하지 못해 신뢰도 낮았음
  • 많은 프로그램이 어셈블리어로 작성되었는데 명령어 집합이 더욱더 풍부해야되고 명령어 자체 기능도 더 강력해야 한다고 여겼음

4.6.4 코드도 저장 공간을 차지한다

  • 실행파일은 기게 명령어와 데이터를 모두 포함하며 디스크 저장 공간을 차지하고, 실행시에는 메모리에 적재 되어 메모리 저장 공간을 차지
  • 옛날 컴퓨터는 메모리의 크기가 몇 KB로 작았는데 더 많은 프로그램을 적재하려면 기계 명령어는 더 세밀하게 설계해서 프로그램이 차지하는 저장공간을 줄였어야 함

아래 3가지 요구 사항을 만족해야 함

  1. 하나의 기계 명령어로 더 많은 작업을 완료할 수 있도록
  2. 기계 명령어 길이 가변적 (간단한 명령어는 짧고, 복잡한 명령어는 길게)
  3. 밀도를 높여 공간을 절약하기ㅇ 위해 고도로 인코딩

4.6.5 필연적인 복잡 명령어 집합의 탄생

당시 산업계의 요구사항을 충분히 만족시켰지만 새로운 문제가 발생

  • CPU명령어 집합은 모두 hardwired (직접 연결) 방식으로 효율적이지만 유연성이 떨어짐

하드웨어를 변경하는 것은 번거롭지만 소프트웨어를 통해 대체 가능 → microcode 등장

단일 복잡 명령어 → 마이크로코드 ROM → 간단한 명령어들

  • 더 많은 명령어를 추가할 때 주요 작업은 마이크로 코드 수정에 집중되며 하드웨어 수정은 거의 필요하지 않기에 CPU 설계 복잡도를 낮출 수 있음

4.6.6 마이크로코드 설계의 문제점

  • 버그 수정이 훨씬 어렵고 마이크로코드 설계가 트랜지스터를 매우 많이 소모
반응형

+ Recent posts