접근

절댓값이 작은 순으로, 만약 같다면 원래 값이 작은(즉 음수 먼저) 출력해야 한다. 이를 위해 절댓값과 기존 값을 가지는 클래스를 선언하고, 정렬 조건을 정의하여 우선순위 큐에 담아줬다. 배열이 비어있는 경우는 곧 큐가 비어있는 경우로 예외 처리를 해줬다. 

 

코드

package week10;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class BJ11286 {
    static class AbsNum implements Comparable<AbsNum>{
        int absVal, val;

        public AbsNum(int absVal, int val) {
            this.absVal = absVal;
            this.val = val;
        }

        @Override
        public int compareTo(AbsNum o) {
            if(this.absVal == o.absVal)
                return this.val - o.val;
            else
                return this.absVal - o.absVal;
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuffer sb = new StringBuffer();

        int n = Integer.parseInt(br.readLine());
        PriorityQueue<AbsNum> pq = new PriorityQueue<>();

        for (int i = 0; i < n; i++) {
            int val = Integer.parseInt(br.readLine());

            if(val != 0)
                pq.add(new AbsNum(Math.abs(val), val));
            else{
                if(pq.isEmpty()) sb.append(0);
                else sb.append(pq.poll().val);

                sb.append("\n");
            }
        }

        System.out.println(sb);
    }
}

'Algorithm' 카테고리의 다른 글

[백준] 13904: 과제 JAVA  (0) 2023.03.20
[백준] 1374: 강의실 JAVA  (0) 2023.03.20
[백준] 1713: 후보 추천하기 JAVA  (0) 2023.03.14
[백준] 5525: IOIOI JAVA  (0) 2023.03.04
[백준] 1541: 잃어버린 괄호 JAVA  (0) 2023.03.03

접근

주어진 작업 순서에 따라 단순히 코드 구현만 하면 되는 문제다. 다만 방향을 고려하는 과정이 좀 헷갈렸는데, 로봇 청소기의 d가 나타내는 값은 시계 방향이고 회전 방향은 반시계 방향이었기 때문이다. 그래서 회전할 때 현재 방향에 3을 더하고 4로 나눈 나머지값을 활용했고, 후진을 할 때는 2를 더했다.

 

코드

package week9;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class BJ14503 {
    static class Pos{
        int x, y, d;

        public Pos(int x, int y, int d) {
            this.x = x;
            this.y = y;
            this.d = d;
        }
    }

    static int[] dx = {-1, 0, 1, 0};
    static int[] dy = {0, 1, 0, -1};
    static int n, m;
    static int[][] room;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;

        // 입력값 저장
        st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        room = new int[n][m];

        st = new StringTokenizer(br.readLine());
        int r = Integer.parseInt(st.nextToken());
        int c = Integer.parseInt(st.nextToken());
        int d = Integer.parseInt(st.nextToken()); // 0~4: 북동남서
        Pos vacuum = new Pos(r, c, d);

        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < m; j++)
                room[i][j] = Integer.parseInt(st.nextToken());
        }
        
        // 청소기 작동
        clean(vacuum);

        // 청소한 칸의 개수 표시
        int sum = 0;
        for (int i = 0; i < n; i++) 
            for (int j = 0; j < m; j++)
                if(room[i][j] == 2) sum++;

        System.out.println(sum);
    }

    private static void clean(Pos vacuum) {
        while(true){
            // 1번 작업
            if(room[vacuum.x][vacuum.y] == 0)
                room[vacuum.x][vacuum.y] = 2;

            boolean needToClean = false;

            // 3번 작업
            for (int i = 0; i < 4; i++) {
                vacuum.d = (vacuum.d+3) % 4;
                int nx = vacuum.x + dx[vacuum.d];
                int ny = vacuum.y + dy[vacuum.d];
                if(!inBound(nx, ny) || room[nx][ny] != 0) continue;

                vacuum.x = nx;
                vacuum.y = ny;
                needToClean = true;
                break;
            }

            // 2번 작업
            if(!needToClean) {
                int backDir = (vacuum.d+2) % 4;
                int nx = vacuum.x + dx[backDir];
                int ny = vacuum.y + dy[backDir];

                // 2-2번 작업
                if(!inBound(nx, ny) || room[nx][ny] == 1) break;
                else{
                    vacuum.x = nx;
                    vacuum.y = ny;
                }
            }
        }
    }

    static boolean inBound(int x, int y){
        return !(x < 0 || y < 0 || x >= n || y >= m);
    }

    static void print(){
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                System.out.print(room[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println();
    }
}

접근

연합을 알기 위해서는 결국 bfs를 통해 조건을 만족하며 인접한 국가들을 모두 찾아야 한다. 대략적인 풀이 순서는 다음과 같다. 

1. 아직 연합에 소속되지 않은 국가를 찾는다

2. 인접한 국가에 대해서 국경을 열 수 있는지 조건을 확인한다

3. bfs로 서로 인접한 모든 국가들을 연합에 소속시킨다

4. 해당 연합의 인구 이동을 반영한다

5. 위 과정을 더 이상 인구 이동이 없을 때까지 반복한다

 

1) 먼저 이차원 visited 배열을 통해 해당 국가가 이미 연합에 속해있는지를 확인하여 불필요하게 반복적인 연산을 피할 필요가 있다. 이때 boolean으로 설정해도 되지만, 나는 이후에 인구 이동을 편하게 계산하기 위해 int로 설정하여 연합의 번호를 저장하도록 했다. 따라서 어떤 연합에도 속하지 않을 경우 0값을 가지게 된다. 이 visited 배열은 새로운 날마다 초기화된다.

 

2, 3) 연합에 속하지 않은 국가를 찾았다면, bfs로 인구수를 비교하며 동일한 연합의 국가들을 찾는다. 인접은 상하좌우 4방향을 찾아야 한다. 만약 인접 인덱스가 전체 배열의 범위를 벗어나거나, 이미 어떤 연합에 속해있을 경우에는 넘어간다. 연합 국가를 찾을 때마다 그 인구수와 개수를 누적한다. 그렇게 연합 번호, 누적 인구수, 전체 국가수를 가진 Union이라는 객체를 만든다.

 

4) 위에서 구한 Union 객체를 통해 인구를 이동시킨다. visited 배열에 저장된 값과 연합 번호가 같은 국가들에 한해서 인구 값을 갱신한다. 

 

5) 만약 Union 객체의 국가 수가 1이라면 연합이 이루어지지 않아 인구 이동이 발생하지 않았다는 것을 의미한다. 따라서 알고리즘 계산을 종료한다. 

 

사실 처음에는 위에서 말한 boolean 방법으로 연합 소속 여부만 확인하고, 별도로 큐에 저장해 그에 속하는 국가들에만 접근하여 인구를 갱신하는 코드를 짰었다. 하지만 실행 시간에 차이가 나지 않았기 때문에 위 방법이 가독성 측면에서는 더 좋다고 생각하여 이렇게 포스팅을 하게 되었다. 

 

코드

package week9;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class BJ16234 {
    static class Pos{
        int x, y;

        public Pos(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    static class Union{
        int unionNum, populationSum, countryCnt;

        public Union(int unionNum, int populationSum, int countryCnt) {
            this.unionNum = unionNum;
            this.populationSum = populationSum;
            this.countryCnt = countryCnt;
        }
    }

    static int[][] countryMap, visited;
    static int n, l, r;
    static int[] dx = {-1, 0, 1, 0};
    static int[] dy = {0, 1, 0, -1};

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;
        
        // 입력값 저장
        st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        l = Integer.parseInt(st.nextToken());
        r = Integer.parseInt(st.nextToken());

        countryMap = new int[n][n];
        visited = new int[n][n];

        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < n; j++)
                countryMap[i][j] = Integer.parseInt(st.nextToken());
        }

        // 인구 이동
        int dayCnt = 0;

        while (checkBorder()) {
            visited = new int[n][n];
            dayCnt++;
        }

        System.out.println(dayCnt);
    }

    private static boolean checkBorder() {
        // 변수 초기화
        boolean isUnion = false;
        int unionNum = 1;

        // 인근 나라 인구 확인
        for (int x = 0; x < n; x++) {
            for (int y = 0; y < n; y++) {
                if(visited[x][y] != 0) continue;

                // 연합끼리 국경 열기
                Union union = openBorder(x, y, unionNum++);

                // 두 개 이상의 국가가 연합이 되었다면 인구이동
                if(union.countryCnt > 1){
                    isUnion = true;
                    movePopulation(union);
                }
            }
        }

        return isUnion;
    }

    private static void movePopulation(Union union) {
        int newPopulation = union.populationSum / union.countryCnt;

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if(visited[i][j] != union.unionNum) continue;
                countryMap[i][j] = newPopulation;
            }
        }
    }

    private static Union openBorder(int x, int y, int unionNum) {
        // 변수 초기화
        Queue<Pos> q = new LinkedList<>();
        int populationSum = 0;
        int countryCnt = 0;

        // 인접한 국가를 bfs로 탐색
        q.add(new Pos(x, y));

        while(!q.isEmpty()) {
            Pos cur = q.poll();
            if(visited[cur.x][cur.y] != 0) continue;

            visited[cur.x][cur.y] = unionNum;
            populationSum += countryMap[cur.x][cur.y];
            countryCnt++;

            for (int d = 0; d < 4; d++) {
                int nx = cur.x + dx[d];
                int ny = cur.y + dy[d];
                if (!inBound(nx, ny) || visited[nx][ny] != 0) continue;

                int populationSub = Math.abs(countryMap[cur.x][cur.y] - countryMap[nx][ny]);
                if (populationSub >= l && populationSub <= r)
                    q.add(new Pos(nx, ny));
            }
        }

        return new Union(unionNum, populationSum, countryCnt);
    }

    private static boolean inBound(int x, int y) {
        return !(x < 0 || y < 0 || x >= n || y >= n);
    }
}

접근

나는 입력 부분과 알고리즘 처리 구분을 구분하기 위해 추천 후보를 따로 배열에 저장했다. 그리고 추천 시 해당 후보를 바로바로 찾기 위해 map을 사용했고 만약 어떤 후보를 지워야 한다면 그냥 map에서 remove해주었다. 

 

어떤 후보를 지워야하는 경우에는 (1)사진틀이 꽉 차있고 (2)이번에 추천받은 후보가 사진틀에 없다는 조건을 만족해야 한다. 사진틀의 최대 개수는 20개이기 때문에 전부 탐색해주었다. n개를 비교하며 추천횟수가 더 적거나, 만약 같다면 더 오래된 후보를 지우도록 한다. 최종 후보 결과를 출력할 때는 배열에 넣고 정렬해도 되겠지만, 나는 우선순위 큐를 사용하여 저장하는 동시에 값이 정렬되도록 했다. 

 

코드

package week9;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class BJ1713 {
    static class Candidate {
        int num; // 후보 번호
        int recommendCnt; // 추천 횟수
        int registerTime; // 사진이 등록된 시점

        Candidate(int num, int registerTime){
            this.num = num;
            this.registerTime = registerTime;
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // 입력값 저장
        int n = Integer.parseInt(br.readLine());
        int m = Integer.parseInt(br.readLine());
        int[] recommendArr = new int[m];

        StringTokenizer st = new StringTokenizer(br.readLine());
        for (int i = 0; i < m; i++)
            recommendArr[i] = Integer.parseInt(st.nextToken());

        // 추천 순서대로 작업
        Map<Integer, Candidate> candidateMap = new HashMap<>();

        for (int i = 0; i < m; i++) {
            int key = recommendArr[i];
            
            // 비어있는 사진틀이 있는지 확인
            if(candidateMap.size() == n && candidateMap.get(key) == null){
                Candidate eraseCandidate = null;

                for(Candidate cur : candidateMap.values()){
                    if(eraseCandidate == null) eraseCandidate = cur;
                    // 추천 횟수가 적은 후보를 지움
                    else if(eraseCandidate.recommendCnt > cur.recommendCnt)
                        eraseCandidate = cur;
                    // 추천 횟수가 같으면 더 오래된 사진을 지움
                    else if(eraseCandidate.recommendCnt == cur.recommendCnt &&
                            eraseCandidate.registerTime > cur.registerTime)
                        eraseCandidate = cur;
                }

                candidateMap.remove(eraseCandidate.num);
            }

            // 추천받은 학생의 사진을 게시
            Candidate candidate = candidateMap.getOrDefault(key, new Candidate(key, i));
            candidate.recommendCnt++;
            candidateMap.put(key, candidate);
        }

        // 최종 후보의 학생 번호를 출력
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        for(Candidate cur : candidateMap.values())
            pq.add(cur.num);

        while(!pq.isEmpty())
            System.out.print(pq.poll() + " ");
    }
}

'Algorithm' 카테고리의 다른 글

[백준] 1374: 강의실 JAVA  (0) 2023.03.20
[백준] 11286: 절댓값 힙 JAVA  (0) 2023.03.20
[백준] 5525: IOIOI JAVA  (0) 2023.03.04
[백준] 1541: 잃어버린 괄호 JAVA  (0) 2023.03.03
[백준] 17472: 다리 만들기 2 JAVA  (0) 2023.02.27

단편화 문제

내부 단편화(interal fragmentation)

  • 메모리를 나눈 크기보다 프로그램이 작아서 들어가지 못하는 공간이 많이 발생하는 현상

 

외부 단편화(external fragmentation)

  • 메모리를 나눈 크기보다 프로그램이 커서 들어가지 못하는 공간이 많이 발생하는 현상

 

메모리 집약/통합

  • 외부 단편화를 해결하기 위한 방법으로 메모리 집약(Compaction)이 있으나 비용이 매우 많이 듦
  • 이외에도 인접한 외부 단편화 두 공간을 합치는 통합(coalescing) 방법도 있음


연속 할당

프로세스를 메모리에 올릴 때 주소 공간을 메모리에 연속적으로 적재하는 방식

고정 분할 방식(fixed partition allocation)

https://zangzangs.tistory.com/133#:~:text=연속할당 기법은 프로세스,분할 방식으로 나뉩니다.

  • 메모리를 미리 나누어 각 분할에 하나의 프로세스를 적재하는 방식
  • 분할의 크기는 모두 동일할 수도 있고 서로 다를 수도 있음
  • 동시에 메모리에 올릴 수 있는 프로그램의 수가 고정되어 있으며 수행 가능한 프로그램의 최대 크기가 제한되어 융통성이 떨어짐
  • 내부 단편화와 외부 단편화 문제가 발생할 수 있음

 

가변 분할 방식(variable partition allocation)

https://zangzangs.tistory.com/133#:~:text=연속할당 기법은 프로세스,분할 방식으로 나뉩니다 .

  • 매 시점 프로그램의 크기에 맞게 동적으로 메모리를 나눠 사용하는 방식
  • 프로그램의 크기를 고려해 메모리를 할당하고 기술적으로 관리할 수 있는 기법이 필요
  • 내부 단편화는 발생하지 않고 외부 단편화는 발생할 수 있음
  • 프로세스 종료 후 메모리에서 삭제되면 해당 공간이 비게 되면서, 새로운 프로그램을 적재하고자 할 때 어떤 가용공간에 올릴 것인지 결정하는 문제(동적 메모리 할당)가 생김

 

동적 메모리 할당 알고리즘

  • 어느 메모리 공간에 프로세스를 올려야 하는지 결정하는 알고리즘
  • Best Fit이 이론적으로는 ‘메모리’를 가장 효율적으로 활용할 수 있는 방법이지만, 그게 최선의 방법이라는 의미는 아님
  • First Fit이 가장 좋은 성능을 보임
알고리즘   동작 방식
First Fit 리스트를 순서대로 스캔하며 크기에 맞는 빈 공간을 찾음
Next Fit 빈 공간들을 기억해 두었다가 차례대로 검색을 시작
Best Fit 모든 엔트리를 탐색하여 가장 근접한 크기를 찾음
Worst fit 항상 가장 큰 빈 공간을 할당
Quick fit 자주 요청되는 공통 크기의 메모리 공간들을 서로 다른 리스트로 관리

 


불연속 할당

- 메모리를 동일한 크기의 페이지(보통 4KB)로 나누고 프로그램마다 페이지 테이블을 두어 이를 통해 메모리에 프로그램을 할당하는 것
- 자세한 주소 매핑 방법은 이쪽 포스팅을 참고

프레임과 페이지

  • 프레임: 주기억장치를 고정된 크기로 나누어 만든 파티션(물리적 개념)
  • 페이지: 프로세스를 고정된 크기로 나누어 만든 조각(논리적 개념)

 

페이징(Paging)

https://luv-n-interest.tistory.com/472

  • 메모리와 프로세스를 동일한 크기의 페이지 단위로 나누어 할당하는 것
  • 동적 메모리 할당 문제가 없어지지만 주소 변환이 복잡해짐
  • 코드, 데이터, 스택 등이 서로 다른 프레임에 쪼개질 수 있기 때문에 권한 작업이 어려워짐
  • 그림에서 p는 page number, d는 page offeset을 나타냄

 

세그멘테이션(segmentation)

https://velog.io/@kdy9kdy/CS-운영체제16-메모리

  • 의미 단위인 세그먼트로 나누는 방식으로, 크기가 동일하지 않음
  • 코드, 데이터, 스택, 힙 등은 물론, main과 같은 함수도 의미 단위로 사용할 수 있음
  • 페이징과 비슷하게 세그먼트 정보를 저장하는 세그먼트 테이블이 프로그램별로 존재하게 됨
  • 공유와 보안 측면에서 좋으며 홀 크기가 균일하지 않은 문제가 발생

 

페이지드 세그멘테이션(paged segmentation)

https://velog.io/@kdy9kdy/CS-운영체제16-메모리

  • 페이지와 세그먼트 방식을 혼합한 것으로, 한 세그먼트가 여러 개의 페이지로 구성됨
  • 홀이 생기는 문제가 없고, 메모리에 페이지 단위로 올라가기 때문에 allocation 문제가 발생하지 않음

 


References

페이지 폴트

정의 및 특징

  • 가상 메모리(프로세스의 주소 공간)에는 존재하지만 실제 메모리인 RAM에는 없는 데이터/코드에 접근할 경우 페이지 폴트가 발생함
  • 페이지 폴트 발생 시 운영체제는 해당 데이터를 메모리로 가져와 마치 페이지 폴트가 발생하지 않은 것처럼 계속해서 동작하게 해줌
  • 스와핑: 각 프로세스의 전체를 가져와 실행하고 다시 디스크에 넣는 방식
  • 가상 메모리: 프로그램의 일부분만이 메모리에 존재해도 실행할 수 있음

 


가상 메모리(virtual memory)

정의 및 특징

  • 한정된 메모리를 효율적으로 활용하기 위해 운영체제가 메모리를 관리하는 기법 중 하나
  • 컴퓨터가 실제로 이용 가능한 메모리 자원을 추상화하여 이를 사용하는 사용자들에게 매우 큰 매모리로 보이게 만드는 것
  • 가상적으로 주어진 주소를 가상 주소(logical address)라고 하며, 실제 메모리 상에 있는 주소를 실제 주소(physical address)라고 함
  • 가상 주소는 메모리 관리 장치(MMU)에 의해 실제 주소로 변환되며, 이 덕분에 사용자는 실제 주소를 의식할 필요 없이 프로그램을 구축할 수 있게 됨
  • 가상 메모리는 가상 주소와 실제 주소가 매핑되어 있고 프로세스의 주소 정보가 들어있는 ‘페이지 테이블’로 관리되며, 속도 향상을 위해 TLB를 사용함

 

메모리 추상화가 없다면

  • (a)와 (b)는 각각 하나의 프로그램이며, 둘이 함께 메모리에 올라간 상황(c)
  • JMP 명령어에서 주소 재배치가 필요해지면서, 결국 여러 프로그램을 함께 실행하기 어려워짐
  • 프로그램이 모든 메모리 바이트에 접근할 수 있고, 운영체제를 포함한 다른 프로그램을 쉽게 지워버릴 수 있다는 점에서 안정성 문제가 존재
  • 가상 주소는 물리적인 저장 위치에 대해서 독립적이기 때문에 주소를 재배치하지 않아도 됨

 


논리/물리주소 변환

BASE/LIMIT 레지스터

  • 각 프로세스의 주소 공간을 물리 메모리의 서로 다른 공간으로 연속적으로 매핑하는 방식
  • 우선 논리 주소와 limit register값(프로그램의 크기)를 비교하여 범위를 벗어난다면 에러가 발생
  • limit보다 작다면 base register값(프로그램의 시작 위치)를 더해 물리 주소를 만듦
  • 일련의 과정은 모든 메모리 접근에서 일어나며, 매번 덧셈과 비교 연산이 요구된다는 단점이 있음(덧셈은 캐리 전파 때문에 시간이 많이 걸림)
  • 이 방식은 초기 컴퓨터에서 많이 사용되었으나 최근에는 CPU가 제공하는 보다 효율적인 방법으로 교체되어 현재는 거의 사용되고 있지 않음

 

페이징 기법

https://lordofkangs.tistory.com/211

  • 이후에 알아볼 페이징 기법은 데이터를 비연속적으로 저장하기 때문에 관리하는 테이블이 필요
  • 비연속적인 물리주소를 연속적인 논리주소로 표현한 것
  • 이러한 변환 과정을 MMU가 수행해주어, 비연속적으로 메모리가 할당되었어도 임의접근(Random-Access)이 가능해짐

 

  • 논리주소의 페이지 번호를 통해 물리주소의 프레임 번호를 파악함
  • 페이징 기법은 주기억장치의 프레임과 프로세스의 페이지를 동일한 크기로 자르기 때문에, 논리주소의 오프셋을 그대로 물리주소로 받아오면 됨
  • 오프셋은 10bit로 되어있으므로 1024가지 주소를 표현할 수 있으며, 이것을 1K라 부름

 


스와핑(swapping)

정의 및 특징

  • 실제로는 모든 프로세스를 메모리에 적재할 수는 없음
  • 페이지 폴트가 발생했을 때 메모리에서 당장 사용하지 않는 영역에 하드디스크의 일부분을 불러와 사용하여 마치 페이지 폴트가 일어나지 않은 것처럼 만드는 것이 스와핑임

 

페이지 폴트와 스와핑의 과정

페이지 vs 프레임
- 페이지: 가상 메모리를 사용하는 최소 크기 단위
- 프레임: 실제 메모리를 사용하는 최소 크기 단위
  1. CPU가 물리 메모리를 확인하여 해당 페이지가 없으면 트랩을 발생해서 OS에 알림
  2. OS는 CPU의 동작을 잠시 멈춤
  3. OS가 페이지 테이블을 확인하여 가상 메모리에 페이지가 존재하는지 확인
    • 없으면 프로세스를 중단하고 현재 물리 메모리에 비어 있는 프레임이 있는지 찾음
    • 물리 메모리에도 없다면 스와핑이 발동됨
  4. 비어 있는 프레임에 해당 페이지를 로드하고, 페이지 테이블을 최신화함
  5. 중단되었던 CPU를 다시 시작함

 


메모리 관리

메모리 조각

  • 빗금 부분은 현재 사용되지 않는 메모리 공간을 나타내며, 이것들을 메모리 조각(Memory Compaction)이라 함
  • 메모리 할당은 프로세스가 새로 메모리에 들어오거나 없어질 때마다 바뀜
  • 흩어져있는 메모리 조각들을 모아 큰 공간으로 합칠 수 있음

 

비트맵을 통한 메모리 관리

  • (b)는 메모리 할당 단위가 1k일 때, 1k 당 1bit로 사용 여부를 표현한 것
  • (c)는 동일한 정보를 linked list로 표현한 것으로, 공간들의 정보를 저장함
  • 만약 프로그램이 4.1kb를 사용한다면 5k를 차지하므로 여전히 메모리 낭비가 존재
  • 할당 단위를 더 작게 한다면 더 많은 메모리를 활용 가능하나, 그만큼 bitmap은 커지게 됨
  • 이러한 검색은 시간이 많이 걸린다는 단점이 있음

 

연결 리스트를 통한 메모리 관리

  • 링크드 리스트를 활용한 방식은 프로세스가 종료되거나 swap out될 때 관리를 쉽게 해줌
  • 그림은 프로세스 X가 제거될 경우, 이웃의 상태에 따른 4가지 종류를 나타냄
  • 단순히 엔트리의 키값을 변경하거나(a), 전체 리스트 엔트리 개수가 줄어듦(b, c, d)으로서 이중 연결 리스트로 구현하는 것이 더 편리하다는 것을 알 수 있음

 


References

메모리 계층

정의 및 특징

  • CPU는 그저 메모리에 올라와 있는 프로그램의 명령어들을 실행할 뿐임
  • 메모리 계층이란 메모리를 필요에 따라 여러 종류로 나눈 것
  • 각 레벨은 하위 레벨의 캐시로서 동작함

 

메모리 종류

메모리  정의  휘발성  속도  기억 용량
레지스터 CPU 안에 있는 작은 메모리 휘발성 가장 빠름 가장 적음
캐시 L1, L2 캐시를 지칭 휘발성 빠름 적음
주기억장치 RAM을 가리킴 휘발성 보통 보통
보조기억장치 HDD, SSD를 일컬음 비휘발성 느림 많음
  • 램은 하드디스크로부터 일정량의 데이터를 복사해서 임시 저장하고 이를 필요 시마다 cpu에 빠르게 전달하는 역할을 함
  • 계층 위로 올라갈수록 가격은 비싸지는데 용량은 작아지고 속도는 빨라짐

 


캐시 메모리

등장 배경

  • 컴퓨터가 발전해감에 따라 프로세서의 발전 속도를 메인 메모리가 따라가지 못함
  • 프로세스가 아무리 빨라도 계산에 필요한 데이터를 얻기 위해서는 메인 메모리에 접근해야 했기 때문에 전체적인 시스템 성능 향상에 한계가 생김
  • 따라서 CPU와 메인 메모리 사이에 캐시를 두게 됨

 

정의 및 특징

  • 빠른 장치와 느린 장치에서 속도 차이에 따른 병목 현상을 줄이기 위한 임시 메모리
  • 데이터를 접근하는 시간이 오래 걸리는 경우를 해결하고 반복된 연산의 시간을 절약할 수 있음
  • 이렇게 속도 차이를 해결하기 위해 두 계층 사이에 있는 계층을 캐싱 계층이라고 함
  • 캐시 메모리와 주기억장치 사이에서 정보를 옮기는 것을 사상(Mapping)이라고 함

 

캐시의 종류

  • L1 캐시: 일반적으로 CPU 칩 안에 내장되어 데이터 사용/참조에 가장 먼저 사용됨
  • L2 캐시: L1보다 느리나 용량이 더 큼
  • L3 캐시: 멀티 코어 시스템에서 코어가 공유하는 캐시

 

캐시의 전송 단위

https://ssoonidev.tistory.com/35

  • 워드(word)
    • CPU의 기본 처리 단위로, 블록/라인을 구성하는 기본 단위임
  • 블록(block)
    • 메모리를 기준으로 잡은 캐시와의 전송 단위
    • 캐시 라인과 크기가 같으며, 메모리 블록은 여러 개의 워드로 구성되어 있음
  • 캐시라인
    • 캐시에 데이터를 저장할 때 특정 자료구조를 사용해 묶음으로 저장하는 것
    • 캐시에 저장하는 데이터에는 메모리 주소 등을 기록해준 태그를 달아놓을 필요가 있으며, 이러한 태그들의 묶음을 캐싱 라인이라고 함
    • 캐시는 여러 개의 캐시 라인으로 이어져 있으며, 메모리로부터 가져올 때도 캐싱 라인을 가져옴

 

지역성의 원리

  • 캐시가 효율적으로 동작하기 위해서는 캐시의 적중율(Hit-rate)를 극대화해야 함
  • 어떤 데이터가 자주 사용되는가에 대한 근거로 사용되는 것이 바로 “지역성
  • 시간 지역성(temporal locality)
    • 특정 데이터가 한번 접근되었을 경우, 가까운 미래에 또 접근할 가능성이 높은 것
    • 예를 들어 for 반복문에서는 계속해서 변수 i에 접근이 이뤄짐
  • 공간 지역성(spatial locality)
    • 최근 접근한 데이터를 이루고 있는 공간이나 그 가까운 공간에 접근하는 특성을 말함
    • 예를 들어 하나의 일차원 배열에 연속적으로 접근하는 경우가 있음

 

캐시히트와 캐시미스

  • 캐시 히트의 경우: 캐시에서 원하는 데이터를 찾음
  • 캐시 미스 & 메모리에서 찾았을 경우: 메모리 → 캐시 메모리 → CPU 레지스터에 복사
  • 캐시 미스 & 메모리에서 찾지 못했을 경우: 보조기억장치 → 메모리 → 캐시 메모리 → CPU 레지스터에 복사
  • 캐시히트의 경우 위치도 가깝고 CPU 내부 버스를 기반으로 작동하기 때문에 속도가 빠름
  • 반면 캐시미스는 메모리에서 가져오며, 시스템 버스를 기반으로 작동하기 때문에 느림

 


캐시 매핑

캐시 매핑의 필요성

  • 캐시에 데이터를 저장했어도 그 위치를 모른다면 시간이 오래 걸리게 됨
  • 캐시매핑이란 주기억장치에서 메모리로 데이터를 전송하는 방법으로 3가지가 존재함
  • 레지스터는 주 메모리에 비하면 굉장히 작고 주 메모리는 굉장히 크게 때문에 작은 레지스터가 캐시 계층으로써 역할을 잘 해주려면 이 매핑을 어떻게 하느냐가 중요함

 

직접 매핑(directed mapping)

  • 데이터가 캐시에 들어올 때 이미 자리가 정해져있는 방식으로, 블록 단위로 저장됨
  • 메모리가 1~100이 있고 캐시가 1~10이 있다면 1:1~10, 2:1~20… 이런 식으로 매핑하는 것
  • 처리가 빠르지만 캐시를 자주 교체해야 해서 적중률이 낮음
  • 메모리 주소 구성
    • 태그: 메인 메모리의 몇번째 블록인지를 알려줌
    • 라인: 같은 위치에 저장되는 00000과 00100을 구분하기 위함
    • 워드: 블록의 크기를 나타냄(00000, 00001)

 

연관 매핑(associative mapping)

  • 직접 매핑의 단점을 보완하기 위해 등장
  • 메인 메모리의 순서와 아무런 관련 없이 저장
  • 모든 블록을 탐색해야 해서 속도가 느리지만 적중률이 높음
  • 혹은, 병렬 검사를 위해 복잡한 회로가 필요함

 

집합 연관 매핑(set associative mapping)

  • 직접 매핑과 연관 매핑을 합쳐놓은 것
  • 메모리는 1~100, 캐시는 1~10이 있다면 캐시 1~5에는 1~50의 데이터를 무작위로 저장시키는 것을 말함
  • 세트 번호로 영역을 탐색하여 연관 매핑의 병렬 탐색을 줄임
  • 모든 라인에 연관 매핑처럼 무작위로 위치하여 직접매핑의 단점도 보완함

 

캐시 쓰기 정책

https://velog.io/@khsfun0312/캐시-정책알고리즘

 

 


캐시의 활용

웹 브라우저의 캐시

  • 보통 사용자의 커스텀한 정보나 인증 모듈 관련 사항들을 웹 브라우저에 저장해 추후 서버에 요청할 때 자신을 나타내는 아이덴티티나 중복 요청 방지를 위해 쓰임
  • 쿠키
    • 4KB까지 데이터를 저장할 수 있는 key-value 저장소
    • 클라이언트 또는 서버에서 만료기한 등을 정할 수 있는데, 보통 서버에서 설정
  • 로컬 스토리지
    • 만료기한이 없는 key-value 저장소로, 클라이언트에서만 수정 가능
    • 10MB까지 저장할 수 있으며 웹 브라우저를 닫아도 유지
  • 세션 스토리지
    • 만료기한이 없는 key-value 저장소로, 클라이언트에서만 수정 가능
    • 탭 단위로 세션 스토리지를 생성하며, 탭을 닫을 때 데이터도 삭제
    • 5MB까지 저장 가능함

 

데이터베이스의 캐싱 계층

  • 데이터베이스 시스템을 구축할 때도 메인 데이터베이스 위에 redis DB 계층을 ‘캐싱 계층’으로 두어 성능을 향상시키기도 함

 


References

컴퓨터의 요소

하드웨어 구성

https://codybuilder.com/27

  • 크게 연산을 담당하는 CPU, 기억을 담당하는 메모리, 입출력 장치로 나눌 수 있음
  • 시스템 버스는 각 요소들과 연결되어 있고 데이터와 명령 제어 신호를 각 장치로 실어 나름

 

데이터 접근 방식

  • 순차접근
    • 데이터를 순차적으로 접근하는 방식
    • 데이터의 위치에 따라 접근 시간이 달라짐
  • 임의접근
    • 어디든 접근할 수 있다고 하여 임의접근, 랜덤접근이라 불림
    • 기억 장소에 관계없이 동일한 접근 시간이 소요됨
    • 임의접근이 잘 되도록 만든 장치가 바로 RAM

 


중앙처리장치(CPU, Central Processing Unit)

CPU의 역할

  • 인터럽트에 의해 단순히 메모리에 존재하는 명령어를 해석해서 실행하는 일꾼
  • 운영체제의 커널이 프로그램을 메모리에 올려 프로세스로 만들면 일꾼인 CPU가 이를 처리함
  • 주기억장치에서 프로그램 명령어와 데이터를 읽어와 처리하고 명령어의 수행 순서를 제어함

 

CPU의 구성

  • CPU는 산술논리연산장치, 제어장치, 레지스터로 구성
  • 산술논리연산장치(ALU): 비교와 연산을 담당
  • 제어 장치: 명령어의 해석과 실행을 담당
  • 레지스터: 속도가 빠른 데이터 기억장소

 

제어 장치(CU, Control Unit)

  • 명령어를 순서대로 실행할 수 있도록 제어하는 장치
  • 컴퓨터를 구성하는 모든 장치들은 제어장치의 제어를 받음
  • 명령어 레지스터에서 프로그램 명렁어를 꺼내 해독하고, 그 결과에 따라 필요한 제어 신호를 연산장치, 기억장치, 입출력장치로 보냄
  • 또한 장치가 보낸 신호를 받아 다음에 수행할 동작을 결정함

 

레지스터

  • CPU 안에 있는 고속 임시 기억장치
  • CPU와 직접 연결되어 있어 연산 속도가 메모리보다 수십~수백 배 빠름
  • CPU는 자체적으로 데이터를 저장할 방법이 없기 때문에 레지스터를 거쳐 데이터를 전달함
  • 명령어 주소, 코드, 연산에 필요한 데이터, 연산 결과 등을 임시로 저장
  • 기능에 따라 다양한 종류로 나눠짐

 

산술논리연산장치(ALU, Arithmetic Logic Unit)

  • 제어장치의 명령에 따라 실제로 연산을 수행하는 장치
  • 덧셈, 뺄셈 같은 두 숫자의 산술 연산과 배타적 논리합, 논리곱 같은 논리 연산을 계산
  • 연산에 필요한 데이터를 레지스터에서 가져오고, 연산 결과를 다시 레지스터로 보냄

 

CPU의 동작 과정

  1. 제어장치가 메모리와 레지스터에 계산할 값을 로드함
  2. 제어장치가 레지스터에 있는 값을 계산하라고 산술논리연산장치에 명령함
  3. 제어장치가 계산된 값을 다시 레지스터에서 메모리로 저장함

 


기억 장치(Memory)

정의 및 특징

  • CPU는 계산을 담당하고, 메모리는 기억을 담당함
  • 메모리가 크면 클수록 많은 일을 동시에 할 수 있음
  • 주기억장치와 보조기억장치로 분류됨

 

주기억장치(Main Memory)

  • 물리적인 디스크가 아니라 컴퓨터 내부에서 현재 CPU가 처리하고 있는 내용을 저장하고 있는 기억장치
  • 보조 저장장치보다 저장 용량이 적지만, 속도가 빨라 실행되는 프로그램이 적재되어 있음
  • CPU는 store과 load의 방식으로 기억장치에 엑세스
  • ROM(Read Only Memory)
    • 전원이 끊겨도 기록된 데이터가 소멸되지 않는 비휘발성 메모리
    • 오직 기억된 데이터를 읽기만 가능한 장치
  • RAM(Random Access Memory)
    • 읽고 쓰기가 가능하며 임의 접근이 가능함
    • 응용 프로그램, 운영체제 등을 불러와 CPU가 작업할 수 있도록 하는 기억장치
    • 전원을 끄면 데이터가 사라지는 휘발성 메모리이나 속도가 빠름

 

보조기억장치(Secondary Memory)

  • 물리적인 디스크가 연결되어 있는 기억 장치
  • 주기억장치보다 속도는 느리지만 가격이 저렴하고 저장 용량이 더 큼
  • 컴퓨터의 전원을 끄더라도 저장된 데이터가 사라지지 않는 비휘발성을 가짐
  • HDD(Hard Disk Driver)
    • 물리적인 디스크를 고속으로 회전시켜 데이터를 저장하는 장치
    • SSD의 등장으로 많이 소멸되는 추세
  • SSD(Solid-State Driver)
    • 흔히 쓰는 USB가 발전된 형태
    • 물리적으로 회전하거나 움직이는 요소가 없으며, 전기적으로 데이터를 저장함
    • 속도가 HDD보다 훨씬 빠르나 가격이 비쌈

 

데이터 적재/저장

  • 니모닉: 기계어를 인간이 인식할 수 있는 것으로 바꾸어주는 것

  • 적재(Load)
    • 기억장치에 저장된 데이터를 읽어 CPU의 레지스터로 적재
    • 주소 버스를 통해 CPU가 요구하는 데이터의 주소값과 제어 버스를 통해 Read 신호가 전달
  • 저장(Store)
    • CPU이 레지스터에서 기억장치의 특정 주소에 데이터를 저장
    • 주소 버스를 통해 특정 주소와 제어 버스를 통해 Write 신호가 전달
  • 워드(Word)
    • CPU가 한 번에 접근하는 데이터를 의미
    • CPU가 지원하는 비트 수와 크기가 같음
    • 32bit CPU에서는 32bit, 64bit CPU에서는 64bit

 


기타

시스템 버스(System Bus)

  • 컴퓨터의 각 구성요소 간 데이터, 신호를 전달하기 위한 데이터 전달 통로

  • 데이터 버스: CPU와 기억장치/입출력장치 사이에서 데이터를 전달하는 ‘양방향’ 버스
  • 주소 버스: CPU가 기억장치/입출력장치에게 데이터를 보내기 위해 주소를 전달하는 ‘단방향’ 버스
  • 제어 버스: 데이터 버스와 주소 버스를 제어하기 위한 신호를 전달하는 ‘양방향’ 버스로, CPU가 Memory를 Read할지, Write할지에 대한 정보를 전달

⇒ 몇 번지에 데이터가 존재하는지 주소를 싣고 다니고(address bus), 해당 번지수의 데이터를 받아 싣고 다니며(data bus), 이 데이터를 보낼건지 받을건지의 신호를 싣고 다님(control bus)
 

DMA 컨트롤러

  • IO 디바이스가 메모리에 직접 접근할 수 있도록 하는 하드웨어 장치
  • CPU에만 너무 많은 인터럽트 요청이 들어오기 때문에 CPU 부하를 막아주며 CPU 의 일을 부담하는 보조 일꾼
  • 하나의 작업을 CPU와 DMA 컨트롤러가 동시에 하는 것을 방지함

 

타이머

  • 몇 초 안에는 작업이 끝나야 한다는 것을 정하고 특정 프로그램에 시간 제한을 다는 역할
  • 시간이 많이 걸리는 프로그램이 작동할 때 제한을 걸기 위해 존재

 

디바이스 컨트롤러

  • 컴퓨터와 연결되어 있는 IO 디바이스들의 작은 CPU를 말함

 


References

+ Recent posts