컴퓨터를 구성하는 기계적 장치
중앙처리장치(CPU), 기억장치(Memory), 입출력장치(I/O Device)로 구성되며 각 장치는 시스템 버스로 연결되어 있음
위 사진에서 각 화살표들을 "시스템 버스"라고 부름
주기억장치와 보조기억장치를 합쳐서 "기억장치"라고 부르며 프로그램을 올려 놓는 기억 공간이라고 생각하면 간단
인간으로 따지면 두뇌에 해당하는 것으로 CPU라고 부른다.
주기억장치에서 프로그램 명령어와 데이터를 읽어와 처리하고 명령어의 수행 순서를 제어한다.
CPU는 제어장치, 연산장치, 레지스터 그리고 이들을 연결하여 데이터를 전달하는 버스로 구성되어 있다.
비교와 연산을 담당하는 산술논리연산장치(ALU)와 명령어의 해석과 실행을 담당하는 제어장치, 그리고 속도가 빠른 데이터 기억장소인 레지스터로 구성된다.
개인용 컴퓨터와 같은 소형 컴퓨터에서는 중앙처리장치를 마이크로프로세서라고도 한다.
컴퓨터에 있는 모든 장치들의 동작을 지시하고 제어하는 장치이다.
제어장치는 명령 레지스터에서 읽어들인 명령어를 해독하여 해당하는 장치에게 제어 신호를 보내 정확하게 수행하도록 지시한다.
제어장치의 구성요소
프로그램 카운터(PC): 다음 실행의 명령을 기억하고 있는 레지스터
명령 레지스터 : 명령 내용과 어드레스를 보관하는 레지스터, 명령부와 어드레스부로 구성됨. 명령부의 코드는 명령 해독기로 보내지고, 어드레스부의 데이터는 주소 해독기로 보내져 해독됨.
제어신호 발생기, 부호기(Encoder): 해독된 명령에 따라 각 장치로 보낼 제어 신호를 생성하는 회로
명령 해독기(Instruction Decoder(ID)): 명령 레지스터에 있는 명령어를 해독하여 부호기로 해독내용을 전송하는 회로
주소 해독기(Address Decoder(AD)): 명령 레지스터의 어드레스부에 기억되어있던 주소 자료를 해독하여 데이터 레지스터로 신호를 보내는 장치
제어장치의 명령에 따라 실제로 연산을 수행하는 장치이다.
산술연산, 논리연산, 관계연산, Shift 등이 있으며 연산장치는 가산기, 누산기, 보수기, 데이터 레지스터, 오버플로 검출기, 시프트레지스터 등으로 구성되어 있다.
종류가 뭐가 있다 정도만 알면 됨
CPU, Memory, I/O 장치 등과 상호 필요한 정보를 교환하기 위해 연결된 공동의 전송선
컴퓨터 내부 회로에서 버스선을 사용하는 목적은 결선수를 줄이기 위해서이다.
Memory나 I/O 장치가 제대로 동작하려면 버스를 통해 전달되는 제어 신호 어드레스 신호 및 데이터 신호의 상호 시간적 관계가 잘 유지되어야 함
데이터, 프로그램, 연산의 중간 결과 등을 일시적 또는 영구적으로 저장하는 장치
CPU가 어떤 작업을 처리하려면 데이터와 데이터를 처리할 프로그램이 필요한데 기억장치가 그 일을 함
기억장치는 접근 속도, 기억 용량, 용도 등에 따라 레지스터, 캐시 메모리, 주기억장치, 보조기억장치로 나누어짐.
종류
레지스터
CPU 내부에 존재하는 기억장치.
접근 시간이 CPU의 처리속도와 비슷
캐시 메모리
CPU가 주기억장치에 접근할 때 속도 차이를 줄이기 위해 사용
실행 중인 프로그램의 명령어와 데이터를 저장
기억 용량은 작지만 접근시간이 주기억장치보다 5~10배 정도 빠름
주기억장치(ex. RAM, ROM)
CPU가 직접 데이터를 읽고 쓸 수 있는 장치
레지스터나 캐시 메모리보다 기억 용량이 큼
ROM(Read Only Memory)
전원이 끊어져도 기록된 데이터들이 소멸되지 않는 비휘발성 메모리
기억된 데이터를 읽기만 가능한 장치
데이터를 한 번 저장하면 수정을 할 수 없음
PROM : 1번 다시 쓰기 가능
EPROM: 무한으로 다시 쓰기 가능
RAM(Random Access Memory)
전원이 끊어지면 데이터가 전부 지워지는 휘발성 메모리
ROM과 달리 쓰기가 가능
응용 프로그램, 운영체제 등을 불러와 CPU가 작업 할 수 있도록 하는 기억장치
데이터를 읽는 속도와 기록하는 속도 같음
프로그램을 로딩하거나 데이터를 임시 저장할때 쓰임
DRAM
SRAM
보조기억장치(ex. HDD, SSD)
주기억장치에 비해 접근 시간은 느리지만 기억 용량이 큼
접근 시간은 주기억장치보다 약 1000배 느림
HDD(Hard Disk Driver)
물리적인 디스크를 고속으로 회전시켜 저장하는 장치
충격에 약하고 소음 발생
SSD(Solid State Driver)
반도체 기반의 정보를 저장하는 기억장치
전기적으로 데이터를 저장하기 때문에 HDD보다 속도가 빠름
하드웨어 구성 요소를 물리적으로 연결하는 선
구성요소가 다른 구성요소로 데이터를 보낼 수 있도록 통로가 되어 줌
용도에 따라 데이터 버스, 주소 버스, 제어 버스로 나누어짐
컴퓨터는 다양한 명령을 통해 정보를 원하는 형태로 처리하고, 목적하는 방향으로 이동시키며, 저장장치에 저장시키는 동작을 수행한다.
이러한 명령들이 모여서 하나의 프로그램을 형성하며, 프로그램들이 모여서 집합을 형성한 것이 소프트웨어다. 즉, 컴퓨터 시스템이나 주변기기 등의 하드웨어를 작동시켜 원하는 작업 결과를 얻기 위한 프로그램 또는 명령어의 거대 집합이다.
컴퓨터 시스템의 운영을 위한 프로그램으로, 컴퓨터 시스템의 개별 하드웨어 요소들을 직접 제어, 통합, 관리하는 가장 큰 기능을 수행한다. 다음 그림은
시스템 소프트웨어의 계층적 분류를 나타낸 것이다.
역할
기능
프로그래밍언어
컴퓨터가 읽고 사용하는 명령이나 코드의 집합
프로그램 개발하는데 사용
고급(High-Level) 언어
명령어가 인간이 사용하는 일상적인 문장에 가까운 언어
고급언어를 기계어로 번역하기 위해서는 복잡한 과정을 거쳐야함
저급(Low-Level) 언어
컴퓨터가 사용하는 언어( 기계어)
0과 1로 표기하는 언어
어셈블리 언어(Assembly Language)
컴파일과 컴파일러
컴파일
컴파일러
데이터베이스 관리 시스템 (DBMS)
데이터의 효율적 관리가 가능
데이터의 접근에 대한 관리 가능
효율적인 데이터 검색 가능
원하는 형태의 보고서로 즉시 작성 가능
백업 및 복구 기능 보유
범용 유틸리티 소프트웨어와 장치 드라이버
범용 유틸리티 소프트웨어
파일 관리 기능
디스크 관리 기능
불필요한 파일을 삭제하거나 문제점을 해결하며 포맷팅 기능을 제공
디스크 조각 모으기를 통해서 작은 조각들을 모아서 파일공간으로 사용하기 쉽도록 만들어 주는 기능도 포함
시스템 상태 보기
장치 드라이버
컴퓨터에게 특정 목적의 작업을 수행하기 위한 프로그램들로 컴퓨터가 많은 다른 작업을 수행할 수 있도록 하는 소프트웨어다.
인간으로 따지면 두뇌에 해당하는 것으로 CPU라고 부름
연산 장치, 제어 장치, 레지스터
연산 장치
산술연산과 논리연산을 수행, 두가지를 모두 수행하기 때문에 산술논리연산장치라고도 말함
연산에 필요한 데이터를 레지스터에서 가져오고, 연산 결과를 다시 레지스터로 보냄
제어 장치
명령어를 순서대로 실행할 수 있도록 제어하는 장치
주기억장치에서 프로그램 명령어를 꺼내 해독한 다음, 해독한 결과에 따라 명령어 실행에 필요한 제어 신호를 기억장치, 연산장치, 입출력장치로 보냄
레지스터
CPU의 속도와 비슷한 고속의 기억장치
명령어 주소, 코드, 연산에 필요한 데이터, 연산 결과 등을 임시로 저장
범용 레지스터와 특수 목적 레지스터로 구분됨
CPU 종류에 따라 사용할 수 있는 레지스터의 개수와 크기가 다름
범용 레지스터: 연산에 필요한 데이터나 연산 결과를 임시로 저장
특수 목적 레지스터: 특별한 용도로 사용하는 레지스터로, 용도와 기능에 따라 구분
메모리 주소 레지스터(MAR): 읽기와 쓰기 연산을 수행 할 주기억장치의 주소를 저장
프로그램 카운터(PC): 다음에 수행할 명령어의 주소를 저장
명령어 레지스터(IR): 현재 실행 중인 명령어를 저장
메모리 버퍼 레지스터(MBR): 주기억장치에서 읽어온 데이터나 주기억장치에 저장할 데이터를 임시로 저장
누산기(AC): 연산 결과를 임시로 저장
명렁어 세트란 CPU가 실행할 명령어의 집합으로서 CPU에 따라 형식과 종류가 서로 다르다. 명령어는 실행할 연산을 나타내는 연산코드(Operation Code)와 연산에 필요한 데이터나 데이터의 저장 위치를 나타내는 피연산자(Operand)로 구성된다.
연산코드는 실행하는 연산의 종류에 따라 4가지 기능으로 나뉨
OP Code(연산 코드)는 길이가 n비트일 때 최대 2^n개의 연산을 정의할 수 있다. 예를 들어 연산의 코드의 길이가 4비트이면 최대 16가지 연산을 정의 할 수 있다.
피연산자는 주소, 숫자, 문자, 논리 데이터 등을 저장할 수 있다.
CPU는 프로그램을 실행하기 위해 주기억장치에서 명령어를 순차적으로 인출하여 해독하고 실행하는 과정을 반복하는데 CPU가 주기억장치에서 한 번에 하나의 명령어를 인출하여 실행하는데 필요한 일련의 활동
인출 사이클
실행 사이클
명령어 사이클을 세분화 하면 인출 사이클, 실행 사이클, 간접 사이클, 인터럽트 사이클로 나눌 수 있다.
인출 사이클과 실행 사이클은 항상 수행되지만 간접 사이클과 인터럽트 사이클은 주소 지정 방식이 필요할 때나 인터럽트 요구가 있을 때만 수행된다.
t0 : MAR <- PC
t1 : MBR <- M[MAR], PC <- PC+1
t2 : IR <- MBR
t0 : MAR <- IR(Addr)
t1 : MBR <- M[MAR]
t2 : AC <- AC + MBR
이미 인출이 진행되고 명령어만 실행하면 되기 때문에 PC값을 증가할 필요가 없음
IR에 MBR의 값이 이미 저장되어 있는 상태임
따라서 AC에 MBR을 더해주기만 하면됨
속도가 빠른 장치와 속도가 느린 장치에서 속도 차이에 따른 병목현상을 줄이기 위한 메모리로 컴퓨터의 처리 속도 향상에 기여한다.
캐시메모리는 주기억장치를 구성하는 DRAM보다 속도가 빠른 SRAM으로 구성하여 전원이 공급되는 상태에서는 기억 내용을 유지하는 임시 메모리다.
프로세서가 매번 메인 메모리에 접근해 데이터를 받아오면 시간이 오래 걸리기 때문에 캐시에 자주 사용하는 데이터를 담아두고, 해당 데이터가 필요할 때 프로세서가 메인 메모리 대신 캐시에 접근하도록해 처리 속도를 높인다.
CPU 코어와 메모리 사이의 병목 현상 완화
웹 브라우저 캐시 파일은, 하드디스크와 웹페이지 사이의 병목 현상을 완화
CPU가 주기억장치에서 저장된 데이터를 읽어올 때, 자주 사용하는 데이터를 캐시 메모리에 저장한 뒤, 다음에 이용할 때 주기억장치가 아닌 캐시 메모리에서 먼저 가져오면서 속도를 향상시킨다.
장점 : 컴퓨터의 처리 속도가 빨라진다.
단점 : 용량이 적고 비싸다.
CPU에서는 2~3개의 캐시 메모리가 사용된다.
각각 L1, L2, L3 라고 부르며 속도와 크기의 차이로 분류 된다.
일반적으로 L1 캐시가 가장 먼저 사용되고 L1에서 데이터를 찾지 못하면 L2로 간다.
L1 Cache: 프로세서와 가장 가까운 캐시. 속도를 위해 I Cache와 D Cache로 나뉜다.
L2 Cache: 용량이 큰 캐시. 크기를 위해 L1 캐시처럼 나누지 않는다.
L3 Cache: 멀티 코어 시스템에서 여러 코어가 공유하는 캐시.
디스크 캐시 : 주기억장치(RAM)와 보조기억장치(하드디스크) 사이에 존재하는 캐시
시간 지역성
공간 지역성
이처럼 참조 지역성의 원리가 존재한다.
캐시에 데이터를 저장할 때는, 이러한 참조 지역성(공간)을 최대한 활용하기 위해 해당 데이터뿐만 아니라, 옆 주소의 데이터도 같이 가져와 미래에 쓰일 것을 대비한다.
CPU가 요청한 데이터가 캐시에 있으면 'Cache Hit'
CPU가 요청한 데이터가 캐시에 없어서 DRAM에서 가져오면 'Cache Miss'
캐시의 성능을 측정할 때는 히트 레이턴시(Hit latency)와 미스 레이턴시(Miss latency)가 중요한 요인으로 꼽힌다.
히트 레이턴시는 히트가 발생해 캐싱된 데이터를 가져올 때 소요되는 시간을 의미한다.
반면 요청한 데이터가 캐시에 존재하지 않는 경우를 캐시 미스(Miss)라고 하며, 미스 레이턴시는 미스가 발생해 상위 캐시에서 데이터를 가져오거나(L1 캐시에 데이터가 없어서 L2 캐시에서 데이터를 찾는 경우) 메모리에서 데이터를 가져올 때 소요되는 시간을 말한다.
평균 접근 시간(Average access time)은 다음과 같이 구한다
캐시의 성능 높이기
Cold miss
해당 메모리 주소를 처음 불러서 나는 미스
Conflict miss
캐시 메모리에 A와 B 데이터를 저장해야 하는데, A와 B가 같은 캐시 메모리 주소에 할당되어 있어서 나는 미스 (direct mapped cache에서 많이 발생)
Capacity miss
캐시 메모리의 공간이 부족해서 나는 미스 (Conflict는 주소 할당 문제, Capacity는 공간 문제)
캐시 크기를 키워서 문제를 해결하려하면, 캐시 접근속도가 느려지고 파워를 많이 먹는 단점이 생김
캐시는 반응 속도가 빠른 SRAM(Static Random Access Memory)으로, 주소가 키(Key)로 주어지면 해당 공간에 즉시 접근할 수 있다.
주소가 키로 주어졌을 때 그 공간에 즉시 접근할 수 있다는 것은 캐시가 하드웨어로 구현한 해시 테이블(Hash table)과 같다는 의미다.
캐시는 블록(Block)으로 구성되어 있다.
주소값 전체를 키로 사용하지는 않고, 그 일부만을 사용한다.
가령 블록 개수가 1024개이고, 블록 사이즈가 32바이트일 때, 32비트 주소가 주어진다면 다음과 같이 인덱싱 할 수 있다.
전체 주소에서 하위 5비트를 오프셋(Offset)으로 쓰고, 이후 10비트를 인덱스(Index)로 사용하여 블록에 접근했다.
인덱스가 10비트인 이유는 개 블록의 모든 인덱스를 표현하기 위해서는 만큼의 비트가 필요하기 때문이다.
여기에선 블록 개수가 개이므로,
이 되어 10비트가 인덱스 비트로 사용되었다.
그러나 이렇게만 하면 서로 다른 데이터의 인덱스가 중복될 위험이 너무 크다.
인덱스의 충돌을 줄이기 위해 주소값의 일부를 태그(Tag)로 사용한다.
블록 개수가 1024개이고, 블록 사이즈가 32바이트일 때, 32비트 주소 0x000c14B8에 접근한다고 가정해보자:
먼저 인덱스(0010100101
)에 대응하는 태그 배열(Tag array)의 필드에 접근한다.
이어서 해당 태그 필드의 유효 비트(Valid bit)를 확인한다.
유효 비트가 1
이라면 태그 필드(00000000000011000
)와 주소의 태그(00000000000011000
)가 같은지 비교한다.
비교 결과(true
, 1
)를 유효 비트(1
)와 AND 연산한다.
유효 비트가 1
이라는 것은 해당 블록에 올바른 값이 존재한다는 의미다. 태그 필드와 주소의 태그가 같고, 유효 비트도 1
이므로 위 예시의 결과는 히트다.
만약 유효 비트가 0
이라면 블록에 값이 없거나 올바르지 않다는 뜻이므로 미스가 발생한다. 그러면 주소의 태그를 태그 필드에 작성하고, 데이터 필드에도 상위 캐시나 메모리에서 요청한 값을 가져와 작성한 뒤, 유효 비트를 1
로 바꿔준다.
유효 비트가 1
이라도 태그가 일치하지 않으면 미스가 발생한다. 이 경우 교체 정책(Replacement policy)에 따라 처리가 달라진다.
먼저 입력된 데이터가 먼저 교체되는 FIFO(First-In First-Out) 정책을 사용한다면 무조건 기존 블록를 교체한다.
태그 배열의 필드를 주소의 태그로 바꾸고, 상위 캐시나 메모리에서 요청한 데이터를 가져와 데이터 필드의 값도 새 데이터로 바꿔준다. (실제로는 요청한 데이터뿐 아니라 그 주변 데이터까지 가져온다.) 기존 데이터는 상위 캐시로 밀려난다.
태그 배열이 추가되면서 더 많은 공간이 필요하게 되었다. 하지만 여전히 '32KB 캐시’는 32KB 데이터를 저장할 수 있는 캐시라는 의미다. 태그를 위한 공간은 블록 크기와 상관없는 오버헤드(Overhead)로 취급하기 때문이다.
1024개의 32B 블록으로 구성된 32KB 캐시의 태그 오버헤드를 구해보자:
즉, 7%의 태그 오버헤드가 발생했다.
공간뿐 아니라 시간 비용도 발생한다. 태그 배열에 접근해 히트를 확인하고, 그 이후에 데이터 배열에 접근해 데이터를 가져오기 때문에 결과적으로 히트 레이턴시가 증가하게 된다.
그래서 두 과정을 병렬적으로 실행한다. 태그 배열에서 히트 여부를 확인하는 동시에 미리 데이터 배열에 접근하는 것이다. 이렇게 하면 히트 레이턴시가 줄어들지만, 미스가 발생했을 때의 리소스 낭비를 감수해야 한다.
2바이트 캐시 블록으로 구성된 8바이트 2-way 캐시가 있고, 4비트 주소가 주어진다고 가정해보자.
메모리 주소 0001
을 참조하는 명령이 실행되었다.
인덱스 비트는 =1이고, 태그 비트는 4−(+1)=2이다.
마지막으로 오프셋 비트는 1이 된다. 따라서 주소 0001
의 인덱스는 0, 태그는 00이다. 즉, 해당 메모리 공간에 위치한 데이터는 인덱스가 0
인 두 공간 중 한 곳에 캐싱될 수 있다.
Way 0
의 블록이 비어있으므로 Way 0
에서 인덱스가 0
인 블록에 데이터를 저장했다.
이때 주소 0010
도 같이 캐싱되었는데, 이는 캐시 히트 비율을 높이기 위해 메모리에서 한 번에 캐시 블록 크기(2B)만큼 데이터를 가져오기 때문이다.
공간 지역성을 활용한 것이다.
여기서는 참조한 데이터(0001
)의 주변 공간인 0010
을 함께 캐싱했다.
메모리 주소 0101
을 참조하는 명령이 실행되었다. 이번에도 인덱스가 0
인 두 공간에 들어갈 수 있다.
하지만 Way 0
에서 인덱스가 0
인 블록은 이미 데이터를 가지고 있기 때문에 데이터가 없는 Way 1
에 캐싱된다. 이번에도 옆에 있는 0110
데이터가 함께 캐싱되었다.
또한 Way 0
에 속한 두 블록의 LRU(Least Recently Used) 값이 증가했다.
이어서 메모리 주소 1000
을 참조하는 명령이 실행되었다. 인덱스가 0
인 두 블록을 확인했으나, 태그가 10
인 데이터가 없어 캐스 미스가 발생했다.
결국 캐싱된 데이터를 교체한다.
두 공간 중 Way 0
블록의 LRU 값이 더 크기 때문에 Way 0
의 첫 번째 블록이 교체 되었고, 참조가 일어났으므로 LRU 값을 0
으로 초기화했다.
참조한 데이터의 주변 공간인 0111
의 데이터도 같은 원리로 캐싱했다. 이때 Way 1
에 속한 두 블록은 참조되지 않았기 때문에 LRU 값이 증가했다.
서로 다른 두 주소가 같은 인덱스를 가지면 충돌이 발생하고, 교체 정책에 따라 블록을 교체한다. 하지만 충돌이 발생할 때마다 캐시 내용을 바꾸면 더 많은 미스가 발생하게 되고, 한 자리의 내용을 끝없이 바꾸는 핑퐁 문제(Ping-pong problem)가 일어날 수 있다.
이 문제는 태그 배열과 데이터 배열을 여러 개 만드는 식으로 개선할 수 있다. 즉, 인덱스가 가리키는 블록이 여러 개가 되는 것이다. 인덱스가 가리키는 블록의 개수에 따라 캐시의 종류를 분류하면 아래와 같다:
Direct mapped: 인덱스가 가리키는 공간이 하나인 경우. 처리가 빠르지만 충돌 발생이 잦다.
Fully associative: 인덱스가 모든 공간을 가리키는 경우. 충돌이 적지만 모든 블록을 탐색해야 해서 속도가 느리다.
Set associative: 인덱스가 가리키는 공간이 두 개 이상인 경우. n-way set associative 캐시라고 부른다.
direct mapped 캐시와 fully associative 캐시 모두 장단점이 극단적이기 때문에 보통은 set associative 캐시를 사용한다.
가장 기본적인 구조로, DRAM의 여러 주소가 캐시 메모리의 한 주소에 대응되는 다대일 방식
현재 그림에서는 메모리 공간이 32개(00000~11111)이고, 캐시 메모리 공간은 8개(000~111)인 상황
ex) 00000, 01000, 10000, 11000인 메모리 주소는 000 캐시 메모리 주소에 맵핑
인덱스 필드 + 태그 필드 + 데이터 필드
로 구성된다.간단하고 빠른 장점이 있지만, Conflict Miss가 발생하는 것이 단점이다. 위 사진처럼 같은 색깔의 데이터를 동시에 사용해야 할 때 발생한다.
Conflict Miss는 A와 B를 동시에 저장해야 하는데 둘이 같은 인덱스에 존재 할 경우 발생한다.
비어있는 캐시 메모리가 있으면, 마음대로 주소를 저장하는 방식
저장할 때는 매우 간단하지만, 찾을 때가 문제
Direct + Fully 방식이다.
특정 행을 지정하고, 그 행안의 어떤 열이든 비어있을 때 저장하는 방식이다.
간단하게 2-way set associative 캐시의 동작을 살펴보자:
주소의 인덱스를 통해 블록에 접근하는 것은 지금까지 본 direct mapped 캐시와 동일하다.
다만 2개의 웨이(Way)가 있기 때문에 데이터가 캐싱되어 있는지 확인하려면 하나의 블록만이 아닌 2개의 블록을 모두 확인해야 한다.
마지막으로 두 웨이의 결과를 OR 연산하면 최종 결과를 낼 수 있다. 모든 웨이에서 미스가 발생하면 교체 정책에 따라 2개의 블록 중 한 곳에 데이터를 작성한다.
direct mapped 캐시와 비교해서 히트 레이턴시를 높이는 대신 충돌 가능성을 줄인 것이다.
데이터를 읽는 동작이 아니라 입력하는 동작이 발생하고, 데이터를 변경할 주소가 캐싱된 상태(Write hit)라면 메모리의 데이터가 업데이트되는 대신 캐시 블록의 데이터가 업데이트된다.
이제 '캐시에서 업데이트된 데이터를 언제 메모리에 쓸 것인가?'에 관한 문제가 생긴다. 여기 두 가지 쓰기 정책(Write policies)이 있다.
dirty
비트를 추가해야 하며, 데이터가 변경되었다면 1
로 바꿔준다. 이후 해당 블록이 교체될 때 dirty
비트가 1
이라면 메모리의 데이터를 변경하는 것이다.데이터를 변경할 주소가 캐싱된 상태가 아니라면(Write miss) Write-allocate 방식을 사용한다. 당연한 얘기지만, 미스가 발생하면 해당 데이터를 캐싱하는 것이다. write-allocate를 하지 않는다면 당장은 리소스를 아낄 수 있겠지만 캐시의 목적을 달성하지는 못할 것이다.
여기까지 로우 레벨에서의 캐시 구조와 동작을 살펴봤는데,
코드 레벨에서 캐시의 효율을 증가시킬 수도 있다.
다음 이중 루프를 보자:
for (i = 0; i < columns; i += 1) {
for (j = 0; j < rows; j += 1) {
arr[j][i] = pow(arr[j][i]);
}
}
2차원 배열의 모든 요소를 제곱하는 루프다. 큰 문제가 없어보이지만 공간 지역성을 따져보면 비효율적인 코드다.
배열 arr
의 요소들은 메모리에 연속적으로 저장되는데, 접근은 순차적으로 이뤄지지 않기 때문이다.
0 4 8 12 16 20 24
+----------+----------+----------+----------+----------+----------+
| [0, 0] | [0, 1] | [0, 2] | [1, 0] | [1, 1] | [1, 2] |
+----------+----------+----------+----------+----------+----------+
i = 0
, j = 0
: 첫 번째 공간 [0, 0]
에 접근한다.
i = 0
, j = 1
: 네 번째 공간 [1, 0]
에 접근한다.
i = 1
, j = 0
: 두 번째 공간 [0, 1]
에 접근한다.
이처럼 공간을 마구 건너뛰며 접근하게 된다.
따라서 아래 코드처럼 외부 루프는 rows
를, 내부 루프는 columns
를 순회해야 한다.
for (i = 0; i < rows; i += 1) {
for (j = 0; j < columns; j += 1) {
arr[i][j] = pow(arr[i][j],2);
}
}
비슷하지만 시간 지역성을 활용하는 예시도 있다.
다음 루프를 보자:
for (i = 0; i < n; i += 1) {
for (j = 0; j < len; j += 1) {
arr[j] = pow(arr[j],2);
}
}
배열 arr
의 모든 요소를 제곱하는 동작을 n
회 반복하는 이중 루프다. 현재 코드는 데이터를 캐싱한 뒤, 다음 접근 때 캐싱한 데이터에 접근한다는 보장이 없다.
전체 데이터가 캐시 크기보다 크다면 배열을 순회하는 과정은 아래 그림과 같을 것이다.
루프의 전반부에서 데이터를 캐싱하지만, 루프가 끝날 때 캐시는 후반부에 접근한 데이터로 덮어씌워진 상태가 된다.
그래서 두 번째 루프를 돌 때는 전반부 데이터를 다시 캐싱해야 한다. 캐싱한 데이터에 다시 접근하기도 전에 캐시 블록 전체가 교체되어 버리는 것이다.
배열 순회 주기를 캐시 크기만큼 끊어주면 문제를 해결할 수 있다.
루프를 도는 횟수는 늘었지만 캐시 히트 비율은 더 높아졌다. 세 번째 루프까지는 전반부 데이터만 처리하고, 그 이후로는 후반부 데이터만 처리하는 것이다.
코드로 구현하면 삼중루프가 된다:
for (i = 0; i < len; i += CACHE_SIZE) {
for (j = 0; j < n; j += 1) {
for (k = 0; k < CACHE_SIZE; k += 1) {
arr[i + k] = pow(arr[i + k], 2);
}
}
}
고정소수점 표현 방식이라는 것은 10진수를 2진수로 바꾼 것을 그대로 표현하는 것이다.
7.625라는 실수를 예로 들면, 2진수로 변환하면 111.101이 될 것이다. 컴퓨터는 이걸 그대로 사용한다.
위의 그림은 16비트 체계를 쓴다고 가정했을 때의 모습이다.
부호 비트 (Sign Bit) : 0이면 양수, 1이면 음수를 뜻한다.
정수부 : 소수점을 기준으로 앞의 부분, 정수부는 뒤에서부터 채운다.
소수부 : 소수점을 기준으로 뒤의 부분, 소수부는 앞에서부터 채우며 남은 뒷자리는 0으로 채운다.
고정 소수점 방식의 장단점
장점 : 구현하기 편리하다. 정수부와 소수부로 표현하여 단순하다.
단점 : 사용하는 비트 수 대비 표현 가능한 수의 범위가 낮아 활용하기 힘들다
부동소수점 표현 방식에서는 2진수로 변환한 결과에서 추가적으로 다음의 과정들을 거친다.
여기서 말하는 정규화는 2진수를 1.xxxx... * 2^n
꼴로 변환하는 것을 말한다.
변환하는 방법은 정수부에 1만 남을 때까지 소수점을 왼쪽으로 이동시키고, 이동한 칸 수만큼 n자리에 집어넣는다.
위에서 봤던 111.101을 정규화하면 1.11101 * 2^2가 된다. 여기서 소수점을 '이동' 시킨다는데서 '부동' 소수점, floating point라는 용어가 나온 것이라고 생각하면 이해하기 쉽다.
IEEE 754 부동소수점 표현
부호 비트(1자리): 0이면 양수, 1이면 음수를 의미한다.
가수부(23자리) : 정규화 결과 소수점 오른쪽에 있는 숫자들을 왼쪽부터 그대로 넣으면된다. 남는 자리는 0으로 채운다.
지수부 : 위의 2^n에서 n에 해당하는 수, 위의 예시에서 2를 2진수로 바꾼 '10'이 해당된다.
💡 IEEE 표준에 따르면 저 부분에는 지수를 그대로 표현하는게 아니라 'bias' 라고 하는 지정된 숫자를 더한 다음 넣어야 한다.
IEEE 표준에서 32비트를 쓰는 경우 bias는 127이라고 규정하고 있다. 따라서 2 + 127 = 129를 2진수로 바꾼 10000001이 들어간다.
결론적으로 7.625는 컴퓨터에서 아래와 같이 저장된다.
이 bias라는 값을 쓰는 이유는 지수가 음수가 되는 경우를 고려해 만든 방법이다.
예를 들어, 0.000101이라는 이진수를 보면, 위의 정규화에 대해서 설명할 때 정수부를 1로
만들어야 한다고 했다. 즉 왼쪽이 아니라 오른쪽으로 소수점을 밀어서 1.01 * 2^-4가 된다.
💡 만약에 bias가 없어서 위에서 2를 그냥 00000010으로 저장했다면, 음수 숫자인 -4는 어떻게 저장해야할까?
부호 비트는 지수의 부호를 뜻하는게 아니라 전체 숫자의 부호를 뜻한다. 따라서 부호비트는 지수의 부호와는 관련이 없다. 지수용 부호 비트를 하나 더 만드는것은 그 나름대로 복잡하다. 따라서 8자리에서 음수랑 양수를 둘 다 표현하기 위해, (10진수 기준으로)
0~127 구간은 음수, 128~255 구간은 양수를 표현하도록 만든 것이다.
참고: 실제로는 0이랑 255는 0이나 0에 한없이 수렴하는 작은 수들, 무한대, NaN -Not a Number- 같은 걸 표현하기 위해서 특별하게 지정되어 있기 때문에 일반적인 표현 범위에
포함되지 않으며, 저런 수들을 표현할 때는 이 글에서 설명한 정규화 방법이 적용되지 않는다고 한다.
위 그림에서 살펴본 32비트 체계를 32비트 단정도 (Single-Precision), 64비트 체계를 64비트 배정도 (Double-Precision) 이라고 부른다. 프로그래밍 언어에서 접하는 실수형 타입 float, double이 각각 전자, 후자에 해당한다.
패리티 비트는 저장된 데이터의 1bit 오류를 검출하는 코드 방식이다.
패리티 비트에는 홀수(odd, 기수)방식, 짝수(even, 우수)방식이 있다.
패리티 비트를 사용하는 이유
컴퓨터의 하드디스크에 데이터는 실질적으로 0과 1 즉 이진수로 변환되어 저장되는데, 우리가 평소에 보는 문서파일들이나 게임 프로그램도 내부적으로는 컴퓨터가 인식하는것은 0과
1로 이루어진 데이터들이다.
데이터는 기억장치에 특정한 크기단위로 저장되어는데 데이터를 저장 또는 읽어올 때에는 주소를 통해 접근하게 된다.
간단한 예로 120이란 값을 8비트의 공간에 저장했다고 가정해보면, 120이라는 숫자값은 내부적으로 2진수로 바뀌어 기억장치의 특정 주소 위치에 저장된다.
120은 2진수로 1111000이다. 그럼 해당 데이터를 저장했을 때 01111000이 저장된다. 다음과 같이 1비트가 남게 되는데, 왼쪽의 빈 공간은 0으로 채운다.
만약 이 상태에서 오류로 인해 첫번째 1이 0으로 바뀌어버리면 어떻게 될까? 혹은 해당 데이터가 사라져버린다면 어떻게 될까?
00111000이 되어 값을 읽어올 때에는 56이란 값으로 들어게 되고, 이상한 데이터가 되거나 아예 읽어올 수도 없게 될 것이다.
이 데이터에 짝수 패리티비트를 적용해 보면 다음과 같다. 이전과 같이 120이라는 값을 이진수로 바꾼 후 1111000
을 앞에 채워 넣는다.
이번에는 맨 뒤 마지막 비어있는 비트를 패리티비트로 사용한다.
짝수 패리티의 경우 데이터의 합이 짝수가 되게 만들어야 한다. 현재 데이터는 1 + 1 + 1 + 1 + 0 + 0 + 0 + 0
으로 짝수이다. 그러므로 맨 뒤 패리티 비트는 0이 된다.
간단하게 1의 개수가 짝수개이면 패리티비트가 0, 홀수개이면 패리티 비트가 1이 된다고 생각하면 된다. 패리티 비트 덕분에 8비트를 모두 조합하면 어떻게 해서든 짝수가 된다. (데이터가 홀수였다면 패리티에 1이 들어가서 결국 짝수가 된다.)
그럼 이 상태에서 첫번째 1이 오류로 인해 소실된다면 어떻게 될까?
그렇습니다 소실된다 하더라도 패리티를 포함한 데이터의 합은 짝수이므로 소실된 데이터가 1이라는 것을 유추할 수 있다. 이처럼 패리티비트를 이용하면1bit의 데이터 오류를 검출할수 있게된다.
💡 그러나 패리티 비트는 2개의 bit 오류가 발생했을 경우는 검출할 수 없다
해밍 부호의 패리티 비트 생성 및 확인에 사용된 각 비트 그룹은 1, 2, 4, 8, 16 등과 같이 2의 거듭 제곱인 숫자로 시작한다.
네 개의 패리티 비트 P1 P2 P4 P8의 위치는 1, 2, 4, 8이며 데이터어 8 비트는 나머지 위치에 있다.
각 패리티 비트는 다음과 같이 계산된다.
P2 = XOR 비트 그룹 (3, 6, 7, 10, 11) = 1 ⊕ 0 ⊕ 0 ⊕ 1 ⊕ 0 = 0
P4 = XOR 비트 그룹 (5, 6, 7, 12) = 1 ⊕ 0 ⊕ 0 ⊕ 0 = 1
P8 = XOR 비트 그룹 (9, 10, 11, 12) = 0 ⊕ 1 ⊕ 0 ⊕ 0 = 1
8비트 데이터와 4 비트의 패리티 비트로 12비트 합성어가 생성된다.
12 비트를 메모리에서 읽어 오류를 검증할 때, 패리티는 패리티 비트를 포함하는 동일한 비트 조합을 통해 검사된다.
예제) 네 개의 체크 비트는 다음과 같이 계산된다.
C1 = XOR 비트 그룹 (1, 3, 5, 7, 9, 11) = 1 ⊕ 1 ⊕ 0 ⊕ 0 ⊕ 0 = 0
C2 = XOR 비트 그룹 (2, 3, 6, 7, 10, 11) = 1 ⊕ 0 ⊕ 0 ⊕ 0 ⊕ 0 = 0
C4 = XOR 비트 그룹 (4, 5, 6, 7, 12) = 1 ⊕ 0 ⊕ 0 ⊕ 0 = 1
C8 = XOR 비트 그룹 (8, 9, 10, 11, 12) = 0 ⊕ 1 ⊕ 0 ⊕ 0 = 1
0 은 짝수 패리티이며 1은 홀수 패리티이다.
비트들은 짝수 패리티로 저장되므로 결과 C = C8C4C2C1 = 0000는 오류가 발생하지 않았음을 나타낸다.
그러나, c가 0이 아닌 경우, 체크 비트에 의해 형성된 4 비트의 이진수는 에러 비트의 위치를 제공한다.
해당 비트의 XOR로 평가하면 네 개의 체크 비트는 아래와 같이 결정된다.
따라서
오류가 없는 경우의 C = 0000
비트 1에 오류가 있는 경우 C = 0001
비트 5에 오류가 있는 경우 C = 0101
C의 이진 값이 0000이 아니면 오류가 있는 비트의 위치를 준다.
2의 n승 번째 자리인 1,2,4번째 자릿수가 패리티 비트라는 것으로 부터 시작한다. 이 숫자로부터 시작하는 세개의 패리티 비트가 짝수인지, 홀수인지 기준으로 판별한다.
1, 3, 5, 7번째 비트 확인 : 0101로 짝수이므로 '0'
2, 3, 6, 7번째 비트 확인 : 0111로 홀수이므로 '1'
4, 5, 6, 7번째 비트 확인 : 1011로 홀수이므로 '1'
역순으로 패리티비트 '110'을 도출했다. 10진법으로 바꾸면 '6'으로, 6번째 비트를 수정하면 된다.
따라서 정답은 00110'0'1이다.
🛠 참고자료
https://dololak.tistory.com/33
https://github.com/gyoogle/tech-interview-for-developer/blob/master/Computer
Science/Computer Architecture/패리티 비트 %26 해밍 코드.md
메모리에 저장된 명령어들을 실행하는 유한 상태 오토마톤(상태 기계)
Processor Core
메모리 장치로 부터 프로그램의 구성 요소인 명령어들을 fetch, decoding, execution 하는 동작을 합니다.
레지스터(Register), 연산장치(ALU), 제어장치(Control Unit), 버스(Bus), Cache memory(Optional), MMU(Optional) 등으로 구성 됩니다.
Processor Core의 종류에는 ARM, MIPS, Intel의 Sandy Bridge 등도 Processor Core의 한 종류 입니다
캐시 메모리, 컨트롤러 등
마이크로프로세서(microprocessor) 또는 초소형 연산처리장치는 컴퓨터의 중앙처리 장치(CPU)를 말한다.
기계어 코드를 실행하기 위해 실행과정을 단계별로 나누어 처리를 위한 마이크로 코드를 작성하고, 이 마이크로 코드에 의해 단계적으로 처리하는 논리회로를 말한다.
마이크로프로세서(micro-processor)는 1개의 조그마한 IC 칩 속에 CPU의 모든 내용을 내장한 칩을 말한다. 이와 같이 CPU의 모든 내용이 하나의 칩 속에 내장됨으로써 가격이 훨씬 싸고, 부피가 줄어든다는 장점이 있다.
중앙처리장치(CPU)가 컴퓨터 전체 시스템의 위치와 입장에서 나온 말이라면, 마이크로프로세서는 동작 방식에서 나온 말로 서로 같은 것이다.
진보된 RISC 기기의 약자로 ARM의 핵심은 RISC이다.
임베디드 기기에 많이 사용되는 RISC 프로세서이다.
저전력을 사용하도록 설계하여 ARM CPU는 모바일 시장 및 싱글 보드 컴퓨터로 불리는 개인용 컴퓨터에서 뚜렷한 강세를 보인다.
단순한 명령 집합을 가진 프로세서가 복잡한 명령 집합을 가진 프로세서보다 훨씬 더 효율적이지 않을까?로 탄생
IBM에서 1980년에 발표하고 MIPS를 창시한 데이비드 패터슨 교수등이 정립한 CPU의 명령어셋 아키텍처와 마이크로 아키텍처 설계에 대해 새로 제시한 개념 내지는 그 개념에 의해 설계된 CPU를 의미한다.
기본적인 개념은 트랜지스터 기반의 메인프레임을 개선하는 과정에서 개발된 아키텍처 개념
<참고> - CISC의 문제점을 해결하기 위한 RISC
- 명령어 길이를 16bit 혹은 32bit로 일정하게 정리하였다.
- 따라서 명령어 해석기는 무슨 명령어인지 별도로 판단할 필요 없이 16bit 혹은 32bit만큼 인출동작을 기계적으로 수행하면 되므로 명령어 해석기가 크게 단순화되었다.
- 1사이클에 1명령어가 정확하게 들어오는 것이 보장되면서 파이프라인에 버블이 낄 가능성이 낮아졌다.
- 명령어 포맷을 일정하게 다듬었다. 예를 들어 소스 레지스터와 타겟 레지스터를 나타내는 플래그를 명령어 상의 특정 비트에 위치시킴으로서 명령어 해석기에서 명령 해석이 완전히 끝나기도 전에 소스 레지스터와 타겟 레지스터를 찾아서 선제적으로 동작시키는 것이 가능해졌다. 이로써 명령어 해석기와 내부 컨트롤의 효율이 증가하였다.
- 메모리를 대상으로 하는 load/store 연산과 레지스터를 대상으로 하는 mov 연산을 분리하면서 메모리 액세스 타이밍의 불확실성을 제거하였다. 즉 필요한 값에 대한 load 동작을 메모리 레이턴시를 고려하여 몇 사이클 앞에서 선제적으로 실행하고 load 동작이 완료되자마자 연산을 수행하는 식으로 레이턴시를 감추는 방식을 사용할 수 있게 되었다. 또한 파이프라인의 효율성도 그에 따라 증가하였다.
- 각 명령어들의 실행 사이클을 1사이클로 조정하였다.
- 어큐뮬레이터 구조를 폐지하였다. 이제 한 개의 명령어는 2개의 소스 레지스터와 1개의 타겟 레지스터를 대상으로 동작하게 되었다. 즉 b=a+b에 종속된 구조에서 탈피하여 c=a+b 동작이 가능해졌다.
- 명령어 해석기 부분이 단순화되고 효율적으로 변경되면서 남아도는 트랜지스터는 더 깊은 파이프라인이나 더 많은 캐시메모리 등에 할당되면서 성능이 향상되었다.
- 가능한 명령어 시퀀스의 경우의 수가 간략해지면서 아키텍처의 정상 동작 검증이 원활해졌다.
<RISC의 단점>
- 단순화를 위해 코드밀도가 감소하여 같은 내용을 처리하는 데 더 많은 코드 용량이 필요하게 되었다
- 항상 16bit 혹은 32bit를 차지하는 고정 길이 명령어는 상황에 따라 8~32bit를 오가는 CISC의 가변 길이 명령어에 비해 코드 밀도 면에서 원천적으로 불리하다.
- 메모리를 대상으로 하는 연산 명령어의 경우 CISC에서는 1개 명령어로 표현 가능하지만 반면 RISC에서는 load-execute-store로 3개의 명령어가 필요하다.
- 마이크로코드로 한 줄로 구현된 CISC 명령어를 몇 개, 혹은 수십개의 RISC 명령어로 변환해야 한다.
- 평균적으로 같은 역할을 수행하는 RISC의 코드 길이는 x86에 비교하면 2배나 길다.
- 메모리를 소스 혹은 타겟으로 쓸 수 없게 되면서 그 역할을 대체해야 하는 더 많은 레지스터가 필요하게 되었다.
- 명령어 길이가 제약되면서 분기 명령의 점프 범위가 제약되었다. CISC에서는 분기 명령의 점프 범위를 단순히 코드 길이를 늘리는 것으로 해결할 수 있지만 RISC 명령어는 그게 불가능하다.
- 가장 현실적인 단점은... x86이 아니더라는 것이었다. 이 시기에서는 PowerPC로 Microsoft Windows를 돌리긴 했지만 ARM으로는 Microsoft Windows를 못 돌렸다
ARM 홀딩스의 사업방식
ARM 홀딩스는 설계에만 관여한다. 명령 집합을 관리하고, 코어 아키텍처의 새 버전을 설계하며 이 설계의 라이센스를 다른 회사에 판매한다
라이센스를 구매한 회사들은 필요에 따라 기능을 향상하거나 적합한 다른 하드웨어와 조합할 수 있다.
즉 ARM의 코어 아키텍처가 프로세서일 뿐이라는 것이다.
나머지는 프로세서 아키텍처를 조합하는 하드웨어 제조업자가 책임지게 된다.
💨 시장에 다양한 종류의 ARM 프로세서가 생겨나게 되었다.
ARM은 칩의 기본 설계 구조만 만들고, 실제 기능 추가와 최적화 부분은 개별 반도체 제조사의 영역으로 맡긴다.
따라서 물리적 설계는 같아도, 명령 집합이 모두 다르기 때문에 서로 다른 칩이 되기도 하는 것이 ARM.
명령집합
- 실행될 소프트웨어, 프로그램에게 프로세서가 제공하는 기본 능력과 기능, 명령의 집합
- 어떤 산술/수치 계산이 사용될 수 있는지와 캐시가 어떻게 할당되어야 할지를, 명령이 어떤 순서로 실행되어야 할지를 결정한다.
소비자에게는 칩이 논리적 구조인 명령 집합으로 구성되면서, 이런 특성 때문에 물리적 설계 베이스는 같지만 용도에 따라 다양한 제품군을 만날 수 있는 특징이 있다.
아키텍처는 논리적인 명령 집합을 물리적으로 표현한 것이므로, 명령어가 많고 복잡해질수록 실제 물리적인 칩 구조도 크고 복잡해진다.
ARM은 RISC 설계 기반으로 '단순한 명령집합을 가진 프로세서가 복잡한 것보다 효율적'임을 기반하기 때문에 명령 집합과 구조 자체가 단순하다.
단순한 명령 집합은, 적은 수의 트랜지스터만 필요하므로 간결한 설계와 더 작은 크기를 가능케 한다.
반도체 기본 부품인 트랜지스터는 전원을 소비해 다이의 크기를 증가시키기 때문에 스마트폰이나 태블릿PC를 위한 프로세서에는 가능한 적은 트랜지스터를 가진 것이 이상적이다.
따라서, 명령 집합의 수가 적기 때문에 트랜지스터 수가 적고 이를 통해 크기가 작고 전원 소모가 낮은 ARM CPU가 스마트폰, 태블릿PC와 같은 모바일 기기에 많이 사용되고 있다.
소비자에 있어 ARM은 '생태계'의 하나라고 생각할 수 있다.
ARM을 위해 개발된 프로그램은 오직 ARM 프로세서가 탑재된 기기에서만 실행할 수 있다. (즉, x86 CPU 프로세서 기반 프로그램에서는 ARM 기반 기기에서 실행할 수 없음)
따라서 ARM에서 실행되던 프로그램을 x86 프로세서에서 실행되도록 하려면 (혹은 그 반대로) 프로그램에 수정이 가해져야만 한다.
즉 프로세서, CPU의 어느 한 명령 집합을 위해 설계된 소프트웨어는 다시 수정되거나 갱신되지 않는 이상 다른 명령 집합에서는 실행될 수가 없다.
하나의 ARM 기기에 동작하는 OS는 다른 ARM 기반 기기에서도 잘 동작한다.
이러한 장점 덕분에 수많은 버전의 안드로이드가 탄생하고 있으며 또한 HP나 블랙베리의 태블릿에도 안드로이드가 탑재될 수 있는 가능성이 생기게 된 것이다.
애플사는 iOS 소스코드를 공개하지 않고 있기 때문에 애플 기기는 불가능하다
ARM을 만드는 기업들은 전력 소모를 줄이고 성능을 높이기 위해 설계를 개선하며 노력하고 있다.