no image
[C++] 백준 6단계 - 25206번 문제 (너의 평점은)
문제설명 소스코드 #include using namespace std; int main() { string majorName; double avg = 0; float sumGrade = 0; double score[20]; float grade[20]; string input; for (int i = 0; i > majorName >> grade[i] >> input; if (input == "A+") score[i] = 4.5; else if (input == "A0") score[i] = 4.0; else if (input == "B+") score[i] = 3.5; else if (input == "B0") score[i] = 3.0; else if (input ==..
2023.04.17
no image
[C++] 백준 6단계 - 1316번 문제 (그룹 단어 체커)
문제설명 소스코드 #include using namespace std; int main() { string input; int N; int count = 0; cin >> N; for (int i = 0; i > input; for (int j = 0; j < input.length(); ++j) { for (int u = 0; u < j; ++u) { if ((input[j] == input[u]) && (input[j] != input[j - 1])){ TF = false; break; } } } if (TF) ++count; } cout
2023.04.16
no image
[C++] 백준 6단계 - 2941번 문제 (크로아티아 알파벳)
문제설명 소스코드 #include using namespace std; int main() { string input; cin >> input; while (true) { if ((input.find("nj") != string::npos) || (input.find("c=") != string::npos) || (input.find("c-") != string::npos) || (input.find("dz=") != string::npos) || (input.find("d-") != string::npos) || (input.find("lj") != string::npos) || (input.find("s=") != string::npos) || (input.find("z=") != string::np..
2023.04.15
no image
[C++] 백준 - 10039번 문제 (평균 점수)
문제설명 소스코드 #include using namespace std; int main() { int sum = 0; for (int i = 0; i > n; if (n
2023.04.15
no image
[C++] 백준 6단계 - 1157번 문제 (단어 공부)
문제설명 소스코드 #include using namespace std; int main() { int arr[26] = { 0 }; int max = 0; int count = 0; int idx = 0; string input; cin >> input; for (int i = 0; i max){ max = arr[i]; idx = i; } //가장 많이 사용된 알파벳 검사 for (int i = 0; i < 26; ++i) i..
2023.04.12
no image
[컴퓨터 구조] CISC와 RISC
이 글은 혼자 공부하는 컴퓨터 구조 + 운영체제 (저자 : 강민철)의 책과 유튜브 영상을 참고하여 개인적으로 정리하는 글임을 알립니다. 세상에는 수많은 CPU 제조사들이 있고, CPU마다 규격과 기능들이 모두 달라서 CPU가 이해하고 실행하는 명령어들은 모두 같지가 않다. 기본적인 명령어의 구조와 작동원리는 비슷하지만 명령어의 세세한 생김새, 주소 지정 방식등은 CPU마다 차이가 있다. CPU가 이해할 수 있는 명령어들의 모음을 명령어 집합(Instruction Set) 또는 명령어 집합 구조(ISA : Instruction Set Architecture)라고 한다. 즉, CPU마다 ISA가 다르다는 것이다. 인텔의 노트북 CPU는 x86 또는 x86-64 ISA를 이해하고, 애플의 아이폰 CPU는 AR..
2023.04.12
no image
[C++] 백준 6단계 - 10988번 문제 (팰린드롬인지 확인하기)
문제설명 소스코드 #include using namespace std; int main() { string input; cin >> input; for (int i = 0; i < input.length() / 2; ++i) { if (input[i] != input[input.length() - 1 - i]) { cout 4번 반복 input[input.length() - 1 - i] 이부분은 i가 증가할 수록 문자열 인덱스의 끝에서 점점 인덱스의 가운데를 비교하게 한다.
2023.04.11
no image
[컴퓨터 구조] 명령어 병렬 처리 기법
이 글은 혼자 공부하는 컴퓨터 구조 + 운영체제 (저자 : 강민철)의 책과 유튜브 영상을 참고하여 개인적으로 정리하는 글임을 알립니다. 빠른 CPU를 만들려면 빠른 클럭 속도에 멀티코어, 멀티스레드를 지원하는 CPU를 만드는 것도 중요하지만 CPU를 놀지 않게하고 효율적으로 작동시키는 것도 중요하다. CPU를 쉬지 않고 작동시키는 방법에는 명령어 병릴 처리기법이 있다. 명령어 병렬 처리기법에는 아래와 같은 종류가 있다. 명령어 파이프라이닝 슈퍼스칼라 비순차적 명령어 처리 명령어 파이프라인 하나의 명령어가 처리되는 전체과정을 클럭단위로 나누어 보면 아래와 같다. 명령어 인출(Instruction Fetch) 명령어 해석 (Instruction Decode) 명렁어 실행 (Execute Instruction..
2023.04.11

문제설명

 

소스코드

#include <iostream>
using namespace std;
int main() {
	string majorName; double avg = 0; float sumGrade = 0;
	double score[20]; float grade[20];
	string input;
	for (int i = 0; i < 20; ++i)
	{
		cin >> majorName >> grade[i] >> input;
		if (input == "A+") score[i] = 4.5;
		else if (input == "A0") score[i] = 4.0;
		else if (input == "B+") score[i] = 3.5;
		else if (input == "B0") score[i] = 3.0;
		else if (input == "C+") score[i] = 2.5;
		else if (input == "C0") score[i] = 2.0;
		else if (input == "D+") score[i] = 1.5;
		else if (input == "D0") score[i] = 1.0;
		else if (input == "F") score[i] = 0.0;
		else if (input == "P") { score[i] = 0.0; grade[i] = 0; }
		avg += (score[i] * grade[i]);
		sumGrade += grade[i];
	}
	avg = avg / sumGrade;
	cout << fixed;
	cout.precision(6);
	cout << avg;
}

 

풀이

  • 전공평점은 전공과목별 (학점 × 과목평점)의 합을 학점의 총합으로 나눈 값이다.
  • 전공 이름은 입력만 받고 따로 처리하지는 않는다.(필요없는 변수이다.)
  • 전공이름, 학점, 과목평점을 입력받는다.
  • 과목평점을 string형 변수 input에 받는다.
  • input은 과목평점이므로 각 맞는 숫자로 변환시켜 배열에 저장한다.
  • avg에 과목평점과 학점을 곱한 값을 모두 더한다.
  • sumGrade에 모든 학점을 더한다.
  • avg에 avg / sumGrade의 값을 대입한다.
  • 소숫점 아래 6자리까지 출력한다.

문제설명

 

소스코드

#include <iostream>
using namespace std;
int main() {
	string input; int N; int count = 0;
	cin >> N;
	for (int i = 0; i < N; ++i) 
	{
		bool TF = true;
		cin >> input;
		for (int j = 0; j < input.length(); ++j) 
		{
			for (int u = 0; u < j; ++u) 
			{
				if ((input[j] == input[u]) && (input[j] != input[j - 1])){ TF = false; break; }
			}
		}
		if (TF) ++count;
	}
	cout << count;
}

 

풀이

  • bool 자료형 TF는 true로 초기화된다.
  • 문자열의 길이 직전 까지 루프를 돌면서(j), 0부터 j 직전 까지 루프를 다시 돈다.(u)
  • 현재 알파벳(j)과 이전의 알파벳(k)이 같으면서, 현재 알파벳(j)과 바로 직전 알파벳(j-1)이 다르면 그룹 단어가 아니다.

문제설명

 

소스코드

#include <iostream>
using namespace std;
int main() {
    string input;
    cin >> input;
    while (true)
    {
        if ((input.find("nj") != string::npos) || (input.find("c=") != string::npos) || (input.find("c-") != string::npos) || (input.find("dz=") != string::npos) ||
            (input.find("d-") != string::npos) || (input.find("lj") != string::npos) || (input.find("s=") != string::npos) || (input.find("z=") != string::npos))
        {
            if (input.find("lj") != string::npos) input.replace(input.find("lj"), 2, "0");
            else if (input.find("nj") != string::npos) input.replace(input.find("nj"),2, "0");
            else if (input.find("c=") != string::npos) input.replace(input.find("c="), 2, "0");
            else if (input.find("c-") != string::npos) input.replace(input.find("c-"), 2, "0");
            else if (input.find("dz=") != string::npos) input.replace(input.find("dz="), 3, "0");
            else if (input.find("d-") != string::npos) input.replace(input.find("d-"), 2, "0");
            else if (input.find("s=") != string::npos) input.replace(input.find("s="), 2, "0");
            else if (input.find("z=") != string::npos) input.replace(input.find("z="), 2, "0");
        }
        else break;
    }
    cout << input.length() << endl;;
}

 

풀이

  • 문자열에 크로아티아 알파벳이 없을 때까지 루프를 돈다.
  • 크로아티아 알파벳이 있으면 해당 알파벳을 "0"으로 만들어서 1글자 취급한다.
  • 문자열의 길이를 출력한다.
string 내장 함수
input.find("문자열") : input에서 "문자열" 이있는 인덱스를 리턴함. "문자열"이 없으면 string::npos(쓰레기값)을 리턴
input.replace(n,m,"문자열") : n번째 인덱스부터 n+m까지 "문자열"로 바꿈

문제설명

 

소스코드

#include <iostream>
using namespace std;
int main() {
    int sum = 0;
    for (int i = 0; i < 5; i++) {
        int n = 0;
        cin >> n;
        if (n <= 40) n = 40;
        sum += n;
    }
    cout << sum / 5;
}

문제설명

 

소스코드

#include<iostream>
using namespace std;
int main() 
{
	int arr[26] = { 0 }; int max = 0; int count = 0; int idx = 0;
	string input;
	cin >> input;
	for (int i = 0; i < input.length(); ++i)
	{
		input[i] = toupper(input[i]); //모두 대문자로 변환
		++arr[input[i] - 65]; //A-65는 0이고, Z-65는 25다
	}
	for (int i = 0; i < 26; ++i) if (arr[i] > max){ max = arr[i]; idx = i; } //가장 많이 사용된 알파벳 검사
	for (int i = 0; i < 26; ++i) if (max == arr[i]) ++count; //가장 많이 사용된 알파벳이 몇개인지 검사
	idx += 65; //idx + 65는 가장 많이 사용한 문자이다.
	if (count > 1) //가장 많이 사용된 알파벳이 2개 이상이면 ? 출력
	{
		cout << "?";
		return 0;
	}
	else cout << (char)idx;
}

 

풀이

  • 입력받은 문자열을 모두 대문자로 바꾼다.
  • 알파벳은 26개이므로 배열 26개를 선언한다.
  • 문자열을 검사하면서 해당하는 문자열의 배열의 값을 증가시킨다.
  • max와 idx에 가장 많이 사용된 배열의 값과 해당 인덱스를 저장한다.
  • 가장 많이 사용된 배열의 값이 2개 이상이면 ?를 출력한다.
toupper()함수는 알파벳을 대문자로 바꿔주는 함수이다.

이 글은 혼자 공부하는 컴퓨터 구조 + 운영체제 (저자 : 강민철)의 책과 유튜브 영상을 참고하여 개인적으로 정리하는 글임을 알립니다.


세상에는 수많은 CPU 제조사들이 있고, CPU마다 규격과 기능들이 모두 달라서 CPU가 이해하고 실행하는 명령어들은 모두 같지가 않다.

기본적인 명령어의 구조와 작동원리는 비슷하지만 명령어의 세세한 생김새, 주소 지정 방식등은 CPU마다 차이가 있다.

CPU가 이해할 수 있는 명령어들의 모음을 명령어 집합(Instruction Set) 또는 명령어 집합 구조(ISA : Instruction Set Architecture)라고 한다.

즉, CPU마다 ISA가 다르다는 것이다.

인텔의 노트북 CPU는 x86 또는 x86-64 ISA를 이해하고, 애플의 아이폰 CPU는 ARM ISA를 이해한다.
같은 소스코드로 만들어진 같은 프로그램이라 할지라도 ISA가 다르면 CPU가 이해할 수 있는 명령어도 어셈블리어도 달라진다.
따라서 같은 명령어라도 한쪽에선 정상작동하지만 다른 한쪽에선 정상작동이 안될 수 있다는 것이다.

 

여러 ISA중에서도 CPU의 성능을 향상시킬 수 있는 방법인 명령어 병렬 처리기법(명령어 파이프라인, 슈퍼스칼라, 비순차적 명령어)을 적용하기 유리한 ISA는 현대 ISA의 양대 산맥인 CISC와 RISC가 있다.

 


 

CISC(Complex Instruction Set Computer)

CISC는 다양하고 강력한 기능의 명령어 집합을 활용하기 때문에 명령어의 형태와 크기가 다양한 가변 길이 명령어를 사용한다.

이는 상대적으로 적은 수의 명령어로도 프로그램을 실행할 수 있다는 것이다.

이런 장점 때문에 메모리를 최대한 아끼며 개발해야했던 시절에는 인기가 많았지만, 활용하는 명령어가 복잡하고 다양한 기능을 제공하기 때문에 명령어의 크기와 실행되기까지의 시간이 일정하지 않다.

또한 복잡한 명령어 때문에 명령어 하나를 실행하는 데에 여러 클럭주기를 필요로 한다.

이는 명령어 파이프라이닝에 큰 어려움을 가져다 준다.

명령어 파이프라이닝이 비교적 힘들다는 것이 CISC의 가장 큰 약점이다.

추가적으로 CISC가 복잡하고 다양한 명령어를 사용할 수 있지만, 대다수의 복잡한 명령어는 사용 빈도가 낮기 때문에 CISC의 전체 명령어중 20%가 사용된 전체 명령어의 80%정도를 차지한다는 연구결과도 있다.

현대의 CISC
명령어 파이프라이닝은 현대 CPU에서는 놓쳐서는 안될 중요한 기술이기 때문에
CPU 내부에서 CISC의 명령어를 쪼개서 파이프라이닝을 가능하게 한다.

 


 

RISC(Reduced Instruction Set Computer)

CISC가 준 교훈은 아래와 같다.

  • 원활한 파이프라이닝을 위해 명령어의 길이와 수행 시간이 짧고 규격화 되어 있어야 한다.
  • 자주 쓰이는 명령어만 줄 곧 사용된다. 복잡한 기능을 지원하는 명령어 보다는 자주 쓰이고 기본적인 명령어를 작고 빠르게 만다는 것이 중요하다.

이런 원칙 하에 등장한 것이 RISC이다.

RISC는 CISC보다 명령어의 종류가 적고, 규격화된 명령어, 되도록 1클럭 내외로 실행되는 명령어를 지향한다.

즉, 짧고 규격화된 고정 길이 명령어를 사용한다.

이러한 점으로 인해 명령어 파이프라이닝에 최적화 되어있다.

추가적으로 메모리에 직접 접근하는 명령어를 load, store 두 개로 제한할 만큼 메모리 접근을 최소화하고 CISC보다 주소 지정 방식의 종류가 적다. 메모리 접근을 최소화 한 만큼 레지스터를 적극적으로 사용한다.

레지스터를 이용하는 연산이 비교적 많고, 범용 레지스터의 개수 또한 더 많다.

사용 가능한 명령어의 개수가 CISC보다 적기 때문에 더 많은 명령어로 프로그램을 작동시킨다.

문제설명

 

소스코드

#include<iostream>
using namespace std;
int main() 
{
	string input;
	cin >> input;
	for (int i = 0; i < input.length() / 2; ++i)
	{
		if (input[i] != input[input.length() - 1 - i])
		{
			cout << 0;
			return 0;
		}
	}
	cout << 1;
}

 

풀이

  • 문자열의 길이가 7이라고 한다면 0번째 인덱스와 6번째 인덱스, 1번째 인덱스와 5번째 인덱스... 이렇게 비교하면 된다.
  • for문은 0부터 문자열 길이의 / 2 까지만 반복하면된다. ex) i = 7 -> 3번 반복, i = 8 -> 4번 반복
  • input[input.length() - 1 - i] 이부분은 i가 증가할 수록 문자열 인덱스의 끝에서 점점 인덱스의 가운데를 비교하게 한다.

이 글은 혼자 공부하는 컴퓨터 구조 + 운영체제 (저자 : 강민철)의 책과 유튜브 영상을 참고하여 개인적으로 정리하는 글임을 알립니다.


빠른 CPU를 만들려면 빠른 클럭 속도에 멀티코어, 멀티스레드를 지원하는 CPU를 만드는 것도 중요하지만 CPU를 놀지 않게하고 효율적으로 작동시키는 것도 중요하다.

CPU를 쉬지 않고 작동시키는 방법에는 명령어 병릴 처리기법이 있다.

명령어 병렬 처리기법에는 아래와 같은 종류가 있다.

  • 명령어 파이프라이닝
  • 슈퍼스칼라
  • 비순차적 명령어 처리

 


명령어 파이프라인

하나의 명령어가 처리되는 전체과정을 클럭단위로 나누어 보면 아래와 같다.

  1. 명령어 인출(Instruction Fetch)
  2. 명령어 해석 (Instruction Decode)
  3. 명렁어 실행 (Execute Instruction)
  4. 결과 저장 (Write Back)

여기서 중요한 점은 같은 단계가 겹치지않으면 CPU는 각 단계를 동시에 실행할 수 있다는 것이다.

https://velog.io/@mrcocoball

위 그림과 같이 CPU는 한 명령어를 인출하는 동안에 다른 명령어를 실행할 수 있고, 한 명령어가 실행되는 동안에 연산 결과를 저장할 수 있다.

이처럼 공장 생산 라인과 같이 명령어들을 명령어 파이프라인에 넣고 동시에 처리하는 기법을 명령어 파이프라이닝이라고 한다.

 

파이프 라이닝은 높은 성능을 가져다 주지만, 특정 상황에는 성능 향상에 실패한다.

이러한 상황을 파이프라인 위험이라고 부른다. 파이프라인 위험의 종류는 아래와 같다.

  • 데이터 위험
  • 제어 위험
  • 구조적 위험

데이터 위험

명령어 간 데이터 의존성에 의해 발생한다.

  • 명령어 1 : R1 <- R2 + R3 // R2 레지스터 값과 R3 레지스터 값을 더한 값을 R1 레지스터에 저장
  • 명령어 2 : R4 <- R1 + R5 // R1 레지스터 값과 R5 레지스터 값을 더한 값을 R4 레지스터에 저장

위와 같은 경우 명령어 1을 수행해야만 명령어 2를 수행할 수 있다.

따라서 명령어 2는 명령어 1의 데이터에 의존적이다.

이처럼 데이터 의존적인 두 명령어를 동시에 실행하려고하면 파이프라인이 제대로 작동하지 않는 것을 데이터 위험이라고 한다.

 

제어 위험

주로 분기 등으로 인한 프로그램 카운터의 갑작스러운 변화에 의해 발생한다.

기본적으로 프로그램 카운터는 현재 실행중인 명령어의 다음 주소로 갱신된다. 하지만 프로그램 실행 흐름이 바뀌어 명령어가 실행되면서 프로그램 카운터 값에 갑작스로운 변화가 생기면 명령어 파이프라인에 미리 가지고 와서 처리 중이었던 명령어는 쓸모가 없어진다.

예를 들어, 11번지, 12번지, 13번지 ... 순으로 명령어 파이프라인이 진행중이었는데 11번지 명령어 실행 도중 갑자기 60번지 명령어를 실행해야 한다면 12~59번지 명령어는 쓸모가 없어진다.

이를 제어 위험이라고 부른다.

이러한 제어 위험을 위해 사용하는 기술중에 분기 예측이 있다. 분기 예측은 프로그램이 어디로 분기할지 미리 예측한 후 그 주소를 인출하는 기술이다.

 

구조적 위험

명령어들을 겹쳐 실행하는 과정에서 서로 다른 명령어가 동시에 CPU내부에 있는 부품을 사용하려고 할 때 발생한다.

구조적 위험은 자원 위험이라고도 부른다.

 


 

슈퍼스칼라

단일 파이프라인 보다는 여러 개의 파이프 라인을 가지는 것이 유리하다.

CPU 내부에 여러 명령어 파이프 라인을 가진 구조를 "슈퍼스칼라" 라고 한다.

https://velog.io/@mrcocoball

슈퍼 스칼라 구조로 명령어 처리가 가능한 CPU를 슈퍼스칼라 프로세서 또는 슈퍼스칼라 CPU라고 한다.

슈퍼 스칼라는 이론적으로 파이프라인 개수에 비례하여 처리 속도가 빨라진다.

하지만 파이프라인 위험 때문에 파이프라인 개수에 비례하여 빨라지진 않는다.

 


 

비순차적 명령어 처리

명령어들을 순차적으로 실행하지 않는 기법으로 데이터 의존성이 있는 데이터의 순서를 바꾸는 방법이다.

즉, 명령어 간 의존 관계가 없는 명령어 등을 먼저 실행 하는 것이다.

  1. M(100) <- 1 //메모리 주소 100에 1을 저장해라
  2. M(101) <- 2 //메모리 주소 101에 2를 저장해라
  3. M(102) <- M(100) + M(102) // 메모리 주소 102에 메모리 주소 100과 102를 더한 값을 저장해라
  4. M(103) <- 1 //메모리 주소 103에 1을 저장해라
  5. M(104) <- 2 //메모리 주소 104에 2를 저장해라

여기서 M(102) <- M(100) + M(102)는 데이터 의존성이 있다.

이러한 명령어를 뒤로 보내버린다.

  1. M(100) <- 1 //메모리 주소 100에 1을 저장해라
  2. M(101) <- 2 //메모리 주소 101에 2를 저장해라
  3. M(103) <- 1 //메모리 주소 103에 1을 저장해라
  4. M(104) <- 2 //메모리 주소 104에 2를 저장해라
  5. M(102) <- M(100) + M(102) // 메모리 주소 102에 메모리 주소 100과 102를 더한 값을 저장해라

이렇게 바꾸면 파이프라인의 중단이 사라지게 되어 CPU의 성능 향상에 도움이 된다.