2023-2학기/컴퓨터구조

[컴구] #01. Introduction

AlriC 2023. 10. 9. 21:00

컴퓨터구조 공부의 특성 상, 전체적인 흐름을 파악하는 것이 굉장히 중요한데요. 그렇기 때문에 이 글은 이제부터 저희가 알아볼 내용에 대해 정말 간단하게 쭉 훑는 느낌입니다. 그래서 전체적인 흐름과 용어 정리 위주로 글이 작성될 예정이라 조금 난잡해보일 수 있는 점 양해 부탁드립니다.

 

컴퓨터구조 분야에서의 7가지 아이디어

지난 60년간 컴퓨터 분야에서는 엄청난 발전이 있었습니다. 이런 과정에서 가장 자주 사용되고, 중요하다고 여겨지는 7가지의 아이디어가 있는데요, 이것부터 하나하나 살펴보도록 합시다.

1. Use Abstraction to Simplify Design : 추상화를 통한 설계 단순화

하드웨어, 소프트웨어 개발자들이 설계를 할 때 아주 디테일한 부분들은 생략을 하고, 상위 수준 모델에 집중을 하는 것입니다. 이렇게 효율적인 설계 방법이 첫번째 아이디어입니다.

2. Make the Common Case Fast : 자주 생기는 케이스를 효율적으로

어떻게 보면 당연한 소리입니다. 자주 생기는 케이스를 빠르게 만드는게, 드물게 생기는 케이스를 빠르게 만드는 것보다 전체적인 성능 개선에 도움이 됩니다. 

3. Performance via Parallelism : 병렬을 통한 성능 개선

작업을 병렬적으로 수행한다는 것. 하나의 큰 작업을 여러 작업으로 쪼개어 작업한다는 뜻입니다. 앞으로 정말 여러가지 예시를 통해 Parallelism, 병렬성을 만나볼 수 있을 거에요.

4. Performance via Pipelining : 파이프라이닝을 통한 성능 개선

Pipelining(파이프라이닝)은 병렬성의 한 형태입니다. 예컨데, 멀리 있는 물가에서 물을 떠올 때, 사람들이 쭉 늘어서서 양동이를 전달하는 것이 사람들이 양동이를 각자 들고 움직이는 것 보다 효율적인거죠.

5. Performance via Prediction : 예측을 통한 성능 개선

다음에 컴퓨터가 받아들일 명령이 어떤 것인지 미리 예측이 가능한 경우, 다음 명령을 예측해서 이를 기다리는 것을 통해서 성능 개선을 만들 수 있습니다. 물론, 예측이 맞을 확률이 비교적 높고 예측이 틀렸을 경우 이를 복구할 때 드는 비용이 비싸지 않을 경우에만 해당하겠죠?

6. Memory Hierarchy : 메모리 계층 구조

메모리라는 단어를 들어보신 분들은 많은데, 이게 정확히 뭘 하는 것인지는 모르시는 분들도 꽤 많을 것 같습니다. 메모리는 기본적으로 기억 장치입니다. 지금 계산을 진행할 때 필요한 값들이라던가, 임시로 저장해둬야 하는 값이라던가, 이런 값들이 전부 메모리에 저장됩니다.

물론 모든 메모리가 전부 빠르고, 크고 값싸면 좋겠지만 그럴 수 없죠. 보통 빠른 메모리는 그 양이 적고, 느린 메모리는 그 양이 많을 수 밖에 없습니다. 그래서 자주 사용하는 값들을 추려서 빠른 메모리에 넣어두고, 자주 사용되지 않는 메모리는 비교적 느린 메모리에 넣어두면 성능이 향상되겠죠? 이 개념은 실제로 컴퓨터 구조에서 아주 중요한 개념이라 나중에 자세히 나옵니다.

7. Dependability via Redundancy : 여유분을 통한 신용도 개선

컴퓨터는 빠르기만해서는 안됩니다. 신뢰할 수 있어야 하죠. 그래서 어떤 부분에 문제가 발생했을 경우 이를 해결할 수 있는 여유분이 있어야 합니다. 

 

소프트웨어 아래

저희가 Python으로 프로그램을 작성할 때, 보통 이렇게 작성을 하죠.

sum = 0
for i in range(n):
	sum += i
print(sum)

근데 이 프로그램을 바로 CPU에 입력한다고 생각해봅시다. CPU가 과연 이 언어를 알아먹을까요? 절대 못 알아먹습니다. 컴퓨터 하드웨어는 아주 단순한, Low-Level의 명령만을 수행할 수 있을 뿐입니다. 복잡한 명령을 컴퓨터가 수행하기 위해서는, 이 High-Level의 명령을 Low-Level의 명령으로 바꿔주는 여러 겹의 소프트웨어가 필요합니다.

이렇게 저희가 일상적으로 사용하는 응용 프로그램을 저희는 Applications Software라고 부릅니다. 그리고 이 응용 프로그램과 하드웨어 사이에 시스템 소프트웨어(System Software)가 있습니다. 이 시스템 소프트웨어에는 여러가지가 있지만, 오늘날 모든 컴퓨터 시스템에서 크게 2가지가 있습니다. 하나는 운영체제, 하나는 컴파일러입니다.

운영체제(Operating System)는 사용자와 하드웨어간의 인터페이스 역할을 합니다. 기본적으로 사용자의 입출력을 처리하고, 메모리를 할당하는 등의 다양한 역할을 수행합니다. 오늘날 사용되고 있는 운영체제의 예시로는 Windows, Linux, iOS, Android 등이 있습니다.

컴파일러(Compiler)는 C++, Java등의 High-Level Language를 하드웨어가 실행할 수 있는 단순한 언어로 번역하는 역할을 합니다. 이에 대한 자세한 내용은 2번째 글에서 설명할 예정이고, 지금은 이에 대해 아주 간단히만 살펴보고 넘어가겠습니다.

하드웨어가 이해할 수 있는 신호는 ON, OFF 단 2가지입니다. 이를 나타내는 수학적인 기호는 바로 0과 1인데요, 그래서 보통 하드웨어가 이해할 수 있는 언어인 기계어는 이진수로 구성되어 있습니다. 여기서 각 문자는 Binary Digit, 또는 Bit라고 부릅니다. 그리고 이 Bit가 모여 섬퓨터가 이해할 수 있는 문자열, 예를 들어 16bit로 이루어진 1001010100101110과 같은 것을 Instruction이라고 합니다. (위 Instruction은 두 숫자를 더하라는 의미를 가집니다.)

물론, 프로그래머들은 저렇게 Instruction을 그대로 작성할 일이 거의 없습니다. 저희가 A + B라고 작성하면 이를 1001010100101110으로 바꿔주는 프로그램이 존재하거든요. 이 과정은 크게 2가지로 분류할 수 있습니다.

먼저 A + B라는 High-Level Programming Language를 기계가 이해할 수 있는 언어인 어셈블리어, 어셈블리 언어(Assembly Language) 로 바꿔주는 프로그램이 컴파일러입니다. 예컨데, A + B를 어셈블리어로 바꾸게 되면 add A, B가 됩니다. 겉보기에는 큰 차이가 없어 보이지만, 어셈블리어는 C, Phython과 같은 High-Level 프로그래밍 언어보다 사용할 수 있는 것들이 제한적입니다.

그리고 어셈블리어를 Bit로 구성된 Instruction으로 바꿔야 하는데, 이 과정을 해주는 프로그램을 우리는 어셈블러(Assembler)라고 부르고, 이렇게 변환된 언어를 Binary Machine Language라고 합니다. 아래는 일련의 과정들을 그림으로 정리한 것입니다.

이런 과정을 거침으로서 얻을 수 있는 여러가지 장점이 있습니다. 첫번째는 프로그래머가 0과 1만 보면서 코딩을 할 필요가 없다는 것이겠죠. 작성된 프로그램이 훨씬 읽기 쉽고, 유지 보수하기도 쉽기 때문입니다. 생산성도 높여주고요. 또한, 사용 목적에 따라 여러 언어가 다양하게 설계될 수 있는데요, 예를 들어 과학 계산용으로 설계된 Fortan, 업무 자료 처리용으로 설계된 Cobol, 기호 조작용으로 설계된 Lisp 등이 이 예시가 되겠네요.

 

하드웨어 아래

지금까지는 저희가 작성한 프로그램 밑에서 동작하는 소프트웨어를 알아보았죠. 지금부터는 하드웨어에 대해서 조금 이야기해보도록 하겠습니다. 컴퓨터 하드웨어는 보통 5개의 구성 요소로 구성됩니다. 입력, 출력, 메모리, 데이터패스, 제어 유닛이 그것인데요, 하나하나 알아보도록 하겠습니다.

먼저 입력 장치(Input Device)출력 장치(Output Device)는 말 그대로 외부에서 정보를 받아오는 역할을 합니다. 가장 대표적으로 그래픽 디스플레이(Liquid Crystal Display - LCD 등) 터치스크린, 카메라, 마이크, 스피커 등이 있겠죠.

데이터패스(Datapath)제어 유닛(Control Unit)을 합쳐서 보통 프로세서, 혹은 CPU(Central Processor Unit)라고 부릅니다. CPU는 프로그래의 지시대로 실제로 일을 하는 부분입니다. 숫자를 더하고, 빼고, 입력 장치와 출력 장치에 신호를 보내 작동을 지시하는 등의 역할을 합니다. 여기에서 데이터패스는 연산을 직접 수행하고, 제어 유닛은 명령어를 읽어 데이터패스, 메모리, 입출력 장치가 무엇을 할지 지시하는 역할을 각각 맡고 있습니다. 

마지막으로 메모리(Memory)는 실행 중인 프로그램과 프로그램이 필요로 하는 데이터를 기억하는 역할을 합니다. 메모리는 DRAM(Dynamic Random Access Memory) 칩으로 구성되어 있습니다. "Random Access" Memory라고 불리는 이유는, 자기 테이프와 같이 순차적으로 정보에 접근해야 하는 다른 메모리와는 다르게, 메모리의 어떤 부분에 접근하더라도 같은 시간이 걸리기 때문입니다.

컴퓨터 하드웨어의 5가지 구성 요소

다시 프로세서, CPU로 돌아오겠습니다. CPU 내부에는 또 다른 종류의 메모리가 존재하는데요. 이를 우리는 캐시 메모리(Cache Memory)라고 부릅니다. 캐시는 SRAM(Static RAM)이라는 또 다른 메모리 기술을 이용하는데요, 간단하게만 말씀드리면 SRAM은 DRAM보다 빠르지만 가격이 비싼 특징을 가지고 있습니다.

지금까지 다뤘던 메모리는 전부 휘발성 메모리(Volatile Memory)입니다. 그 말은, 전원이 끊어지면 저장된 내용이 전부 사라진다는 뜻입니다. 컴퓨터를 껐다 키면 열려 있던 파일과 프로그램 등이 전부 사라지는 게 이 이유 때문입니다. 그런데 컴퓨터를 재부팅한다고 해서 컴퓨터의 모든 파일이 사라지는 것은 아니잖아요? 이렇게 사라지지 않는 데이터들은 비휘발성 메모리(Nonvolatile Memory)에 저장되기 때문입니다.

보통 컴퓨터에 쓰이는 휘발성 메모리를 메인 메모리(Main Memory)라고 부르며, 비휘발성 메모리를 보조기억장치(Secondary Memory)라고 부릅니다.

하드웨어와 소프트웨어는 서로 특정 언어를 통해 통신을 합니다. 이 때 이 언어를 구성하는 단어들을 명령어라고 하고, 이 언어 자체를 Instruction Set Architecture, 단순하게 Architecture라고 합니다.

 

성능 이란?

저희가 컴퓨터나 어떤 시스템을 보고 성능이 좋다, 나쁘다는 이야기를 많이 하잖아요? 그런데 사실 어떤 시스템의 성능을 측정하는 것은 쉬운 일이 아닙니다.

예를 들어 240명을 태울 수 있고 564mph의 속도로 날 수 있는 보잉 737 비행기와, 132명을 태울 수 있고 1350mph로 날 수 있는 BAC가 있다고 생각합시다. 어떤 비행기가 더 좋은 비행기인가요? 속도의 관점에서 성능을 정의하자면 BAC가 더 좋은 비행기겠지만, 여행 항공사 입장에서는 더 많은 승객을 태울 수 있는 보잉 737 비행기가 더 좋은 비행기가 되겠죠.

이처럼 컴퓨터의 성능도 여러 방법으로 정의할 수 있습니다. 보통의 사용자에게 가장 중요한 요소는 바로 작업을 시작하고 그 작업을 끝내기까지의 시간. 즉 실행시간(Execution Time)입니다. 만약 여러 대의 서버로 많은 사용자의 작업을 처리하는 서버 관리자의 입장에서는 하루 동안 얼마나 많은 작업을 처리할 수 있는지, 즉 처리량(Throughput)이 더 중요합니다. 다만 저희는 실행시간에 초점을 맞춰보겠습니다.

컴퓨터의 성능이 좋다는 것은 실행시간이 짧다는 것을 의미합니다. 그래서 우리는 '성능'과 '실행시간'은 역수 관계에 있다고 생각할 수 있습니다. 만약 같은 프로그램이 A에서 10초, B에서 15초만에 실행되었다고 생각해봅시다. 그러면 A의 성능을 1/10, B의 성능을 1/15라고 표현할 수 있고 이는 A가 B보다 1.5배 빠르다는 것을 의미합니다. B가 A보다 1.5배 느리다고 할 수도 있겠네요.

단, 주의할 점은 여기서 실행시간은 하나의 작업을 끝내는데 걸리는 시간이므로 메모리에 접근하고, 입력과 출력을 하는 등의 시간이 전부 포함된 것입니다. 따라서, 우리는 CPU가 순수하게 이 프로그램을 실행하기 위해 소비한 시간을 계산할 필요가 있습니다. 이 시간을 CPU Execution Time, 혹은 CPU Time이라고 부릅니다. 입출력 시간 등 전체 실행시간을 기준으로 한 성능을 시스템 성능, 순수 CPU Time으로만 계산한 성능을 CPU 성능이라고 부르는데요, 저희는 CPU 성능에 초점을 맞출 것이지만, 시스템 성능에도 똑같이 적용 가능한 내용들입니다.

 

거의 모든 컴퓨터에서 연산은 연속적으로 진행되는 것이 아니라, 클럭(Clock)이라 불리는 주기적인 이벤트?를 기준으로 이루어집니다. 이 클럭의 시간 간격을 클럭 사이클(Clock Cycle), 클럭 주기(Clock Period)등으로 부릅니다. 그리고 1초에 클럭이 몇 번 일어나는 지를 클럭 속도(Clock Speed)라고 합니다. 예를 들어 클럭 사이클이 250ps인 컴퓨터가 있다면, 클럭 속도는 4GHz가 됩니다.

그러면 프로그램을 실행하기 위해 CPU가 소비한 시간, CPU Time은 이 프로그램을 실행할 때 지난 클럭의 개수와 클럭 사이클을 곱해서 구할 수 있습니다.

CPU Time = 클럭의 개수 × 클럭 사이클 = 클럭의 개수 / 클럭 속도

그러면 결론이 나오죠. CPU Time을 줄이기 위해서는 2가지 방법이 있습니다. 클럭 사이클의 길이를 줄이거나, 클럭의 수를 줄이거나. 보통 프로그램은 여러 줄의 Instruction으로 구성되기 때문에, "클럭의 개수 = 명령어의 수 × 명령어 당 클럭의 개수"라고 생각할 수 있습니다.

여기서 어떤 명령어는 빨리 연산이 될거고, 어떤 명령어는 느리게 될건데, 명령어 별로 클럭의 개수가 다 다르지 않냐? 라고 질문하실 수 있습니다. 맞습니다. 그래서 위와 같이 계산할 때는 명령어 당 "평균" 클럭의 개수를 이용하는데요, 이를 Clock Cycles per Instruction, 줄여서 CPI라고 부릅니다.

 

예시로 문제를 하나 보겠습니다. 

2GHz 클럭의 컴퓨터 A에서 10초만에 실행되는 프로그램이 있다. 이 프로그램을 6초 동안에 실행할 컴퓨터 B를 설계하고자 한다. 클럭 속도는 얼마든지 빠르게 만들 수 있지만, A에 비해 1.2배 많은 클럭이 필요하다. 컴퓨터 B의 클럭 속도는 얼마여야 하는가?

 

풀이를 보려면 아래를 열어주세요!

더보기
먼저 A에서 이 프로그램을 실행할 때 필요한 클럭의 개수를 먼저 구해보겠습니다.
CPU Time = 클럭의 개수 / 클럭 속도 이므로, 클럭의 개수 = CPU Time × 클럭 속도입니다.
따라서 A에서 필요한 클럭의 개수는 10초 × 2GHz = 20 × 10^9개 입니다.

조건으로부터 B에서는 A에 비해 1.2배 많은 클럭이 필요하다고 했으므로, B에서 필요한 클럭의 개수는 24  × 10^9개 입니다.
마지막으로 위의 식을 다시 이용해 B의 클럭 속도를 구하면 되겠죠?
클럭 속도 = 클럭의 개수 / CPU Time = 24  × 10^9 / 6초 = 4GHz

 

문제 하나만 더 보고 넘어가겠습니다.

어떤 프로그램을 두 컴퓨터 A, B에서 실행시킨다. 컴퓨터 A의 클럭 사이클 시간은 250ps이고 CPI는 2.0이며, B의 클럭 사이클 시간은 500ps, CPI는 1.2이다. 어떤 컴퓨터가 얼마나 더 빠른가?

 

풀이를 보려면 아래를 열어주세요!

더보기
한 프로그램을 두 컴퓨터에서 실행시키는 것이므로, 실행해야 하는 Instruction의 수는 같을 것입니다. 이를 y라고 하겠습니다. 그러면 각 컴퓨터에서 소요되는 클럭의 개수는 아래와 같습니다.
A에서 필요한 클럭의 개수 : 2.0y
B에서 필요한 클럭의 개수 : 1.2y

이제 CPU Time = 클럭 사이클 × 클럭의 개수임을 이용해 각 컴퓨터의 CPU Time을 구하면 아래와 같습니다.
A의 CPU Time = 250ps × 2.0y = 500y ps
B의 CPU Time = 500ps × 1.2y = 600y ps

실행 시간이 짧은 A가 성능이 더 좋다는 것은 확실합니다. A가 얼마나 더 빠른지를 계산하기 위해서는 B의 CPU Time을 A의 CPU Time으로 나누어줍니다. 
600y/500y = 1.2 이므로, A가 B보다 1.2배 빠르다고 할 수 있습니다.

 

전력 장벽과 Multiprocessor

Intel CPU의 클럭 속도와 소비 전력 변화

위 그래프는 시간이 지나면서 Intel CPU의 클럭 속도와 소비 전력이 어떻게 변화해왔는지를 나타낸 그래프입니다. 앞에서 말했듯 클럭 속도의 증가는 곧 성능의 증가를 의미합니다. 1초에 더 많은 Clock을 진행시킬 수 있으니깐요. 그런데, 보통은 클럭 속도가 증가할수록 소비 전력이 증가합니다. 그런데 최근에 그 성장세가 주춤하고 있죠? 그 이유는 일반적으로 CPU에서 사용되는 냉각 시스템에 한계가 있기 때문에, 실제로 사용할 수 있는 전력에 상한선이 존재하기 때문입니다. 그래서 클럭 속도 증가가 멈춘 상황입니다. 이렇게 전력, Power의 한계 때문에 성능 증가가 멈추는 이 상황을 Power Wall이라고 합니다.

회로에서 에너지를 소비하는 주 원인은 동적 에너지(Dynamic Energy)입니다. 이 동적 에너지는 트랜지스터가 0에서 1, 혹은 그 반대로 바뀔 때 소비되는 에너지를 뜻합니다. 이 문장이 뭘 의미하는지 자세히 이해하지 못했어도 큰 관계가 없습니다. 중요한 것은 이 동적 에너지가 Capacitive Load($c.l.$)와 전압($V$)의 제곱에 비례한다는 것입니다.

$$E=c.l.\times V^2$$

이 에너지는 트랜지스터가 스위칭할 때 발생하는 에너지이므로, 위 식에 시간 당 스위칭이 몇 번 발생하는 지 ($freq$)를 곱해주면 전력을 구할 수 있습니다. (전력은 시간 당 전류가 하는 일의 양으로, 다르게 말하면 시간 당 에너지입니다.)

$P = c.l.\times V^2\times freq$

감이 오시겠지만 트랜지스터의 스위칭 빈도 $freq$는 클럭 속도가 높을 수록 높습니다. 그럼 다시 위쪽에 있는 그래프를 볼까요? 1982년에서 2004년까지 클럭 속도가 300배 가까이 증가했지만, 이에 반해 전력은 3.3에서 103으로 30배 정도만 증가한 모습입니다. 이렇게 전력 소모량에 비해 클럭 속도가 많이 상승할 수 있었던 이유는 그 만큼 새로운 공정 기술을 통해 전압이 낮아졌기 때문입니다.

이 시점에서 개발자들은 전압을 더 낮추어 전력 소모량을 유지하며 클럭 속도를 늘릴 수 있는 방법을 연구하고 있었습니다. 그러나 전압을 너무 낮추면 트랜지스터에서 누설되는 전류가 너무 많아져서 이 역시 불가능한 상황입니다. 그래서 하드웨어 설계자들은 성능을 올리기 위해 다른 방법을 택했습니다. 하나의 프로세서의 성능을 계속 향상시키는 대신, 여러개의 프로세서를 집적한 Microprocessor를 만든 것입니다.

이 때 생기는 용어 혼란을 줄이기 위해, 하나의 프로세서를 코어(Core)라고 부르고, 이들을 합쳐 만든 Microprocessor를 멀티 코어 마이크로프로세서(Multicore Microprocessor)라고 부릅니다. "쿼드 코어" 마이크로프로세서는 4개의 코어를 합친 칩이 됩니다.

 

Amdahl's Law

Amdahl의 법칙은 컴퓨터의 일부분만 개선했을 때 전체 성능이 얼마나 좋아지는가에 대한 법칙입니다. 

Amdahl's Law

예를 들어, 실행에 100초가 걸리는 프로그램의 80초가 곱하기에 소요된다고 합시다. 이 때 프로그램이 5배 빠르게 실행되게 하려면 곱셈 속도를 얼마나 개선해야 할까요?

개선해야 하는 속도, improvement factor를 미지수로 두고 나머지 값을 대입하게 되면, 아래와 같습니다.

$$100 = \frac{80}{n}+20$$

그런데 이 식을 만족하는 $n$은 존재하지 않습니다. 그 말은 곱셈 속도의 증가만으로 위 프로그램을 5배 빠르게 만들 수는 없다는 뜻입니다. 

 

마무리

이번 글에서는 컴퓨터 구조에 대해 알기 위해 알아야 할 가장 기초적인 용어들을 정리해보고, 중요 개념들에 대해서 살짝씩 알아보았습니다. 

감사합니다.


이 글은 컴퓨터공학과 학부생이 개인 공부 목적으로 작성한 글이므로, 부정확하거나 틀린 내용이 있을 수 있으니 참고용으로만 봐주시면 좋겠습니다. 레퍼런스 및 글에 대한 기본적인 정보는 이 글을 참고해 주세요.