일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- 암시적변환
- RAII
- 일반화복사생성자
- 스택풀림
- c++
- 제네릭프로그래밍
- uniqueptr
- EffectiveC++
- 부분복사
- 템플릿
- 교차dll문제
- effectivec++.
- RB트리
- private상속
- most vexing parse
- sharedptr
- 복사함수
- 가상기본클래스
- 정점버퍼
- 게임프로그래밍
- 상수객체참조
- operator=
- 해골책
- 이른진단
- Directx9
- 암시적인터페이스
- fvf
- rcsp
- 도우미함수
- 멤버함수템플릿
- Today
- Total
성공할 게임개발자
[C] 0. 기본 프로그래밍의 역사 본문
누가 명령어를 읽는가?
컴퓨터의 모든 연산은 중앙처리장치 CPU(Central Processing Unit) 에서 처리
대충 1초에 10억번 개의 연산처리
그래픽 관련 연산 전문적으로 처리하는 GPU(Graphics Processing Unit)
행렬 연산 전문, 그래픽과 관련된 명령어는 CPU와 비교할 수 없게 빠른 속도로 처리
CPU => 똑똑한 애들 한 두명이 일함
GPU=> 조금 덜 똑똑한 애들 수백 수천명이 모여서 일을 함
어디서 명령어를 읽는가?
CPU가 명령어를 실행하기 위해서 두가지 조건 필요
1. 실행할 명령어를 읽어야 함
2. 연산된 결과를 어디엔가 저장
이상적으로는 CPU에서 실행, 결과 저장 다 하면 좋지만 CPU는 가연산에 특화 되어 데이터 저장공간이 매우부족함
CPU가 연산을 수행하기 위해서 데이터를 저장하는 공간을 레지스터(Register)라고 부름. 64비트 CPU의 경우 일반적인 연산을 수행할 수 있는 레지스터는 총 16개 밖에 없음
레지스터는 CPU안의 메모리 이기 때문에 CPU안에서 연산 수행 시 매우 빠르게 접근 가능하지만 기술 한계상 레지스터의 개수를 늘리는 것은 어렵기 때문에 CPU 밖에 있는 저장 공간이 필요할 수 밖에 없음
그것이 바로 RAM(Random Access Memory)
CPU가 필요한 데이터를 꺼내 쓰거나 저장하는데 단점은 휘발성메모리 라는 점
휘발성 메모리란? 전기가 있을 경우만 유지되는 메모리, 전원 미공급 시 메모리 저장 데이터 손실
전원이 공급되지 않아도 데이터를 유지할 수 있는 저장장치는 하드 디스크와 SSD 하지만 데이터를 읽어들이는 작업은 RAM보다 훨씬 느림
통상적으로
CPU에서 램에 있는 데이터를 가져오는데 100나노초 (0.0000001초)
SSD의 경우 50마이크로초~150마이크로초 (0.00005초 ~ 0.00015초)
HDD의 경우 1~10밀리초 (0.001초 ~ 0.01초)
CPU는 평균적으로 0.3나노초 마다 1연산을 수행하는데, 하드 디스크에서 데이텉가 올 때 까지 기다리는 동안 3천만번의 연산 수행가능 만약 CPU가 매번 HDD에서 필요한 명령어를 읽어 들인다면 엄청난 시간낭비
따라서,
1. 하드 디스크에서 저장되어 있는 프로그램의 위치를 찾아서 램에 복사
2. CPU는 램에서 명령어를 읽어들여서 실행
순으로 진행
사실 CPU에서 RAM에 접근하는 속도도 빠른게 아님. CPU에서 RAM에 있는 데이터를 가져오는 시간인 100나노초면 CPU가 0.3나노초마다 1연산을 수행하니까 대강 333연산 수행 시간 => 생각보다 엄청 김
따라서 빠르게 데이터를 레지스터에 불러올 수 있는 저장 공간으로 캐시(Cache)를 사용
캐시(Cache)는 계층별로 L1, L2, L3 캐시로 이루어져 있는데 L1의 경우 크기가 제일 작지만(256KB) 레지스터와 가장 인접한 캐시로 데이터를 읽는데 1나노초 밖에 걸리지 않음
L3의 경우 대강 28나노초 걸리지만 크기가 제일 큼(~16MB) 따라서 지금 가장 필요한 데이터의 경우 L1캐시에 들어가게 되고 중요도에 따라서 낮을수록 L2, L3에 배치
CPU는 조만간 사용할 거 같은 데이터를 미리 캐시에 불러오고 램에 데이터를 저장할 때 바로 램에 쓰는 것이 아니라 캐시에 잠깐 써놨다가 램에 적는 방식을 사용
CPU가 미래에 실행할 명령어들을 미리 알 수 없기 때문에 RAM에서 어떤 데이터가 필요할 지는 알수없음. 따라서 CPU는 여러 예측 알고리즘을 사용해서 캐시의 적중률을 높히려고함. 만약 CPU가 요청한 데이터가 캐시에 없다면 캐시 미스(Cache miss)라고하여 램에서 필요한 데이터를 불러오느라 상당한 시간 지체됨. 빠르게 동작하는 프로그램을 설계하기 위해 캐시 미스 확률을 낮게하는것이 중요!
정리
CPU가 부엌이라면
● 연산하는 것은 도마위에서 재료들을 손질하는것
● 처음재료는 냉장고(RAM)에 있으므로 꺼내는데 꽤나 시간이 걸림
● 하지만 재료들을 도마 옆에 준비한 이상 (CPU Cache) 금방금방 옆에 있는 재료들을 도마위에 올려서 썰수있음
냉장고에 원하는 재료가 없다면 근처에서 사와야함(SSD, HDD) 느낌적으로 설명하명 SSD는 미국가서 재료 가저오는것 이고 HDD는 씨앗 부터 추수하는 꼴
명령어는 어떻게 작성?
CPU가 램에서 데이터를 읽어들이기 위해 램의 어디에서 데이터를 읽어들일지 말해줘야함. 램에 있는 모든 데이터는 1바이트(8비트) 단위로 0번을 시작으로 고유의 주소(Address)가 부여
CPU는 램에게 어디에서 데이터를 읽을지 알려준다면 RAM은 해당 위치에 있는 데이터를 즉각 전달
어디에다 데이터를 저장할지 알려준다면 RAM은 해당 위치에 있는 데이터를 CPU가 전달할 데이터로 바꿔치기(그 위치의 데이터는 삭제)
중요한 점은 얼마만큼 읽어야 할지도 말해줘야 한다는 점. RAM상에는 데이터의 경계가 없기 때문에 0x1234라는 주소값에서 1바이트만 읽어야 할 수도 있고 4바이트를 읽어야할 수도 있음 이것은 명령어 단계에서 지정해줌
예를들어 CPU에게 주소값 0x1234에 1바이트 만큼 3이란느 데이터를 저장하고 싶다면
● CPU레지스터에 접근하고자 하는 주소값 0x1234를 저장. 편의상 a라는 레지스터에 저장했다고 생각
● a에 저장된 주소값에서 부터 1바이트 부분까지 3을 저장해라는 명령어 수행
그렇다면 CPU는 레지스터a에 저장되어있는 주소값인 0x1234에 3이라는 데이터를 저장하라는 명령을 실행. 따라서 RAM에는 3이라는 데이터가 들어감
위 명령을 실제 사용되는 CPU명령어로 재현하면
mov eax, 4660 # 4660 은 0x1234 를 십진수로 나타낸 것입니다.
mov BYTE PTR [rax], 3
mov는 대입 해라 라는 명령어
명령어를 해석해 보자면
1. eax라는 레지스터에 0x1234라는 값을 대입해라
2. rax라는 이름의 레지스터에 들어있는 값을 주소값으로 생각해서 해당 위치에 3을 대입해라.
BYTE PTR은 전달할 주소값으로부터 1바이트 만큼의 데이터를 우리가 저장할 값(3)으로 덮어씌우라는 의미
rax와 eax는 같은 레지스터를 의미, 64비트 CPU에는 64비트 크기의 레지스터 16개가있는데 rax는 그중하나, eax는 rax레지스터의 마지막 32비트를 의미
0x1234의 경우 크기가
2^32 보다 작기 때문에 레지스터의 마지막 32비트 안에 기록할 수 있음. 따라서 레지스터에서 eax, rax 아무거나 읽어도 똑같이 0x1234가 읽힘
만약 1바이트 말고 4바이트 영역에 3을 쓰고 싶다면
mov eax, 4660
mov DWORD PTR [rax], 3
DWORD PTR 의 경우 4바이트 만큼 보라는 의미
와 같이 0x1234부터 0x1237까지 0x003이라는 데이터가 들어가게 됨
CPU가 명령어를 읽어들이는 방법
CPU는 주소값을 통해 램에 어디에 접근할지 명령
하지만 CPU는 독자적으로 명령하지 않고 명령어를 어디선가 가져와야함. 이와 같이 CPU에 실행할 명령어를 제공하는 것을 "프로그램을 실행한다" 라고함
CPU는 현재 램의 어디에서 명령어를 읽어야 할지 계속 알아야하기 때문에 RIP(Register Instruction Pointer) 를 사용함
예를 들어 컴퓨터에서 스타크래프트 실행 시 운영체제가 HDD, SDD와 같은 저장 장치에 있는 스타크래프트 프로그램 파일을 메모리에 복사하는 작업 수행
CPU는 그저 명령어, 데이터 인지 생각 안하고 명령 수행하고 RIP를 업데이트 하는것만 수행
우리는 여러 프로그램을 실행한다
예시와 같이 여러 프로그램을 실행함
하지만 문제 발생
mov eax, 4660
mov DWORD PTR [rax], 3
와 같은 명령어 수행시 ( 0x1234(4660)에 위치해 있는 곳에서 4바이트 만큼 공간에 3이라는 값을 쓰라는 명령)
만약 그 공간을 다른 프로그램이 사용중이라면 다른 프로그램의 데이터를 손상시키게 됨
이를 해결하기 위해서 페이징(Paging) 개념이 들어감
CPU가 참조하는 메모리 주소값을 가상메모리(Virtual Memory)라고하고 변환 과정에 의해 참조하게될 실제 메모리의 주소값을 물리메모리(Physical Memory)라고함
이러한 변환 방식을 페이징이라고 하고 변환 되는 최소의 메모리 단위를 페이지(Page)라고 함
어떻게 변환을 수행할 지 기록한 테이블을 페이지 테이블(Page Table)이라고하고 각 프로그램 마다 하나씩 가지고 있음. 따라서 구글 크롬에서의 0x1234와 그림판의 0x1234는 실제로는 다른 물리 메모리 주소를 참조함
정리
- 모든 연산은 CPU 에서 수행된다. 정확히 말하자면, CPU 의 자그마한 레지스터 상에서 수행된다. 64 비트 CPU 의 경우 레지스터의 크기는 8 바이트 이다.
- CPU 는 무슨 연산을 할 지 알려주는 명령어와, 명령어를 실행하기 위해 필요로 하는 데이터를 메모리 (램) 에서 읽는다.
- 우리가 프로그램을 실행한다는 것은 하드 디스크에 잠들어 있는 명령어들과 데이터를 메모리에 쓰는 것이라 생각하면 된다. 그리고 운영체제가 CPU 에 처음으로 실행해야 할 명령어의 주소값을 전달함으로써 프로그램이 시작된다.
- CPU 에는 캐시가 있어서 메모리 접근 횟수를 줄일 수 있다.
- 각 프로그램들은 마치 자신이 방대한 메모리 공간 전체를 사용하는 것 처럼 생각하며 작동한다.
- CPU 에서 참조하는 주소값은 실제 물리 메모리 주소값이 아니라 가상 메모리 주소값이다.
- 가상 메모리 주소값은 각 프로그램의 페이지 테이블을 통해서 실제 메모리 주소값으로 변환된다.
'C' 카테고리의 다른 글
[C] 3. 포인터 (0) | 2025.02.12 |
---|---|
[C] 2. switch문이 왜 필요한데? if문 쓰면 되잖아 (0) | 2025.02.11 |
[C] 1. 변수 크기 (0) | 2025.02.11 |