본 포스팅은 '파이썬 알고리즘 인터뷰(책만, 박상길)을 참조함

 

원형 큐 디자인

1. 문제 - 원형 큐 디자인(리트코드 622)

문제 정의

원형 큐(Circular Queue)를 구현하는 문제다.

세부적으로 구해야하는 함수는 아래와 같다

  • 특정 요소(value)를 삽입하는 enQueue(value)
  • 맨 앞의 요소를 제거하는 deQueue()
  • 맨 앞의 요소를 출력하는 Front()
  • 맨 마지막 요소를 출력하는 Rear()
  • 원형 큐가 비어 있는지 여부를 판단하는 isEmpty()
  • 원형 큐가 가득 차 있는지 여부를 판단하는 isFull()
- Input
    ["MyCircularQueue", "enQueue", "enQueue", "enQueue", "enQueue", "Rear", "isFull", "deQueue", "enQueue", "Rear"]
    [[3], [1], [2], [3], [4], [], [], [], [4], []]
- Output
    [null, true, true, true, false, 3, true, true, true, 4]

- Explanation
    MyCircularQueue myCircularQueue = new MyCircularQueue(3);
    myCircularQueue.enQueue(1); // return True
    myCircularQueue.enQueue(2); // return True
    myCircularQueue.enQueue(3); // return True
    myCircularQueue.enQueue(4); // return False
    myCircularQueue.Rear();     // return 3
    myCircularQueue.isFull();   // return True
    myCircularQueue.deQueue();  // return True
    myCircularQueue.enQueue(4); // return True
    myCircularQueue.Rear();     // return 4

원형 큐 개념

일반적인 큐는 공간이 가득 차면 요소를 더이상 추가할 수 없다.

deQueue()로 앞부분의 값이 빠져 공간이 비어있어도 추가하는 방법이 없다.

반면, 원형 큐는 공간이 가득차도, 앞 부분이 비어있으면 요소를 추가할 수 있다.

image

원형 큐의 구현 방식은 기본적으로 투포인터와 유사하다.

큐에 요소가 삽입될 때마다 ‘rear’포인트가 움직이고, 요소가 제거되면 ‘front’포인터가 움직인다.

삽입 또는 제거 명령에 따라 포인터가 움직이다가, 두 포인터가 한 지점에서 만나면 큐에 모든 공간이 가득 차게 된다.

위 그림 두 번째 줄에서 세 번째 큐를 보면 rear 포인터가 돌면서 원형 큐에 요소를 가득 채웠음을 알 수 있다.

하지만 다음 그림에서 보듯이 큐 앞부분에 빈 공간을 이용하여 새로운 요소를 삽입하게 된다.

2. 코드

초기화 함수

def __init__(self, k):
        """
        :type k: int
        """
        self.q = [None] * k
        self.maxlen = k 
        self.p1 = 0
        self.p2 = 0

k개의 공간을 가지는 원형 큐의 초기화 함수를 정의한다.

우선 원형 큐에 k개의 None값으로 채워주며, 당연히 최대 길이는 k이다.

앞서 말한 ‘front 포인터’는 p1, ‘rear 포인터'는 p2로 정의하며 초기값을 0으로 할당한다.

삽입

def enQueue(self, value):
        """
        :type value: int
        :rtype: bool
        """
        if self.q[self.p2] is None:
            self.q[self.p2] = value
            self.p2 = (self.p2 + 1) % self.maxlen
            return True

        else: 
            return False

매개변수로 삽입할 값인 value를 설정한다.

rear 포인터(p2)가 가리키는 삽입 위치가 비어있다면 해당 위치에 value값을 지정한다.

포인터를 +1만큼 이동시켜야 하는데, 주의할 점은 최대 길이로 나눈 나머지로 계산하는 것이다.

원형큐 구조이니 만큼, ‘인덱스 5’에서 +1이 될 경우, ‘6’이 아닌 ‘0’이 되어야 하기 때문이다.

제거

def deQueue(self):
        """
        :rtype: bool
        """
        if self.q[self.p1] is None:
            return False

        else: 
            self.q[self.p1] = None
            self.p1 = (self.p1 + 1) % self.maxlen
            return True

front 포인터(p1)가 가리키는 제거 위치가 비어있지 않다면, 해당 값을 지워준다.

삽입 함수와 동일하게 포인터를 +1 만큼 추가하되 모듈로 연산으로 원형큐에 적합한 인덱스로 변환해준다.

전체 코드(나머지 코드 포함)

class MyCircularQueue(object):

    def __init__(self, k):
        """
        :type k: int
        """
        self.q = [None] * k
        self.maxlen = k 
        self.p1 = 0
        self.p2 = 0


    # EnQueue: rear 포인터(p2) 이동
    def enQueue(self, value):
        """
        :type value: int
        :rtype: bool
        """
        if self.q[self.p2] is None:
            self.q[self.p2] = value
            self.p2 = (self.p2 + 1) % self.maxlen
            return True

        else: 
            return False


    def deQueue(self):
        """
        :rtype: bool
        """
        if self.q[self.p1] is None:
            return False

        else: 
            self.q[self.p1] = None
            self.p1 = (self.p1 + 1) % self.maxlen
            return True


    def Front(self):
        """
        :rtype: int
        """
        return -1 if self.q[self.p1] is None else self.q[self.p1]

    def Rear(self):
        """
        :rtype: int
        """
        return -1 if self.q[self.p2-1] is None else self.q[self.p2-1]

    def isEmpty(self):
        """
        :rtype: bool
        """
        return self.p1 == self.p2 and self.q[self.p1] is None

    def isFull(self):
        """
        :rtype: bool
        """
        return self.p1 == self.p2 and self.q[self.p1] is not None

# Your MyCircularQueue object will be instantiated and called as such:
# obj = MyCircularQueue(k)
# param_1 = obj.enQueue(value)
# param_2 = obj.deQueue()
# param_3 = obj.Front()
# param_4 = obj.Rear()
# param_5 = obj.isEmpty()
# param_6 = obj.isFull()

본 포스팅은 '파이썬 알고리즘 인터뷰(책만, 박상길)을 참조함

1. 문제 - 일일온도(리트코드 739)

- 문제: 매일 화씨 온도 리스트 T를 입력받아서, 더 따뜻한 날씨를 위해서는 며칠을 더 기다려야 하는지를 출력하라

    - Input: temperatures = [73,74,75,71,69,72,76,73]
    - Output: [1,1,4,2,1,1,0,0]

- 설명: 위 온도 배열에서 첫 번째 값은 73도이고 그 다음은 74도다. 날씨가 더 따뜻해지려면 1일만 기다리면 되므로 출력값은 ‘1’이된다.

2. 풀이 - 스택 값 이용

개요

전체적인 풀이 과정은 아래와 같다

  1. (for 반복문) for 반복문으로 온도 리스트를 참조한다.
  2. (스택) 이를 통해 요소의 인덱스를 스택에 저장한다.
  3. (while문) 현재 온도(for문)가 이전 온도(스택)보다 큰지 여부를 판단한다
  4. (결과값) 현재 온도와 이전 온도의 인덱스 차이(=날짜 차이)를 리턴한다

해설

우선 아래 그림으로 연산 과정을 살펴보겠다.

image

F’는 입력값인 온도 리스트를 말한다. 

for 문을 통해 인덱스5(온도 72)가 참조 되었다고 하자.

이 때, 인덱스5보다 기온이 낮은 인덱스4(69도)와 인덱스3(71도)는 스택에서 대기를 하고 있는 상태다. 

‘for문이 가리키는 인덱스 5의 온도’가 ‘스택의 마지막 값이 가리키는 인덱스4’의 온도보다 크기 때문에

인덱스4는 스택에서 제거되고 인덱스5와 인덱스4의 차이값(날짜 차이) 1이 출력된다.

스택에서 인덱스4가 출력된 후, 스택의 맨 마지막에는 인덱스 3이 남게된다.

while문을 통하여 앞서 말한 과정과 동일하게 인덱스5와 인덱스3 간의 날짜 차이가 구해진다.

코드

def dailyTemperatures(self, temperatures):
  """
  :type temperatures: List[int]
  :rtype: List[int]
  """

  stack = []

  result = []

  for i, f in enumerate(temperatures):

      # 현재 온도 > 스택 온도 => (인덱스)거리를 정답으로 등록
      while stack and temperatures[stack[-1]] < f:
          prev = stack.pop()
          result[prev] = i - prev

      stack.append(i)
      result.append(0)

  return result

<목차>

1. 개념

2. 예제

 

 

 

1. 개념


분할정복(Divide and Conquer)이란 문제를 직접 해결하기 쉬운 단위로 잘게 나눈 후(분할) 작은 문제의 답을 구하여(정복) 저장하고,

 

작은 문제의 답을 조합하여 전체 문제를 해결하는 방법이다.

 

아래 그림과 수도코드에서 분할, 정복, 조합에 해당하는 부분을 표시해보았다.

 

분할 정복 그래프 및 수도 코드

 

 

 

 

2. 예제 -과반수 엘리먼트(리트코드 169)


1) 문제 

 

배열을 중에서 전체 엘리먼트의 절반 이상을 차지하는 값을 출력할 것을 요구한다.

 

입력 및 출력

위 예시를 보면, 리스트 내 7개의 엘리먼트 중 '정수 2'가 4개로  절반 이상을 차지하므로

 

결과 값으로 2가 출력된 것을 알 수 있다.

 

 

 

2) 풀이

 

앞서 개념에서 설명한 것처럼 분할 정복 기법은 분할, 정복, 조합 부분으로 이뤄진다.

 

전체 코드는 다음과 같으며 분할, 정복, 조합 파트로 세분화하여 살펴보겠다.

 

 

 

(1st) 분할

재귀 함수를 호출할 때, 슬라이싱을 활용하여 입력값(리스트, nums)의 범위를 조정한 한다.

 

하나의 배열을 두 개로 쪼개는데,

 

첫 번째 배열(a)은 왼쪽 절반([ : len(nums) // 2] ), 두 번째 배열은 오른쪽 절반( [len(nums) // 2 : ] )으로 슬라이싱 처리한다.

 

최종적으로 분할된 모습은 아래와 같을 것이다.

 

 

 

 

(2nd) 정복

끝까지 쪼개면 배열에 값이 하나만 존재할 것이다.

 

따라서 배열의 길이가 1이 되면 그 값(정수형)을 반환한다.

 

 

 

(3rd) 조합

 

정복으로 얻은 하위 문제 해답을 조합하여 전체 문제의 답을 구해나간다.

 

왼쪽 하위 문제에서 절반 이상을 차지하던 값(a)이 상위 문제 배열에서도 절반 이상(   > len(nums) // 2   )을 차지할 때, 

 

True(정수 1)가 반환되므로 결국 a가 리턴된다.

 

반대의 경우는 b가 리턴될 것이다. 

 

 

이처럼 하부 문제를 구하는 과정을 반복하여 전체 문제의 해답을 도출한다.

운영체제 '가상 메모리' 파트에서 중요하게 다뤄지는 스레싱에 관해 살펴보려고 한다.

 

< 목차> 

1. 의미

2. 발생 과정

3. 스레싱 방지 기법

 

 

1. 의미


스레싱(Thrasing)이란 개별 프로세스에 할당된 메모리 양이 적어서 페이지 부재율(Page Fault Rate)이 크게 상승해 CPU 이용률 (CPU Utilization)이 낮아지는 현상을 말함

 

 

 

 

2. 발생 과정


스레싱이 발생하는 과정은 다음과 같다.

 

우선 메모리에 할당된 프로세스 수가 적은 경우를 가정해보자.

 

운영체제는 CPU 이용률을 높이기 위해 메모리에 더 많은 프로세스를 적재하게 되며,

 

이에 따라 다중 프로그래밍 정도(Multi-programming Degree: MPD)가 증가한다.

 

MPD가 일정 수준 이상으로 높아지면 한정된 메모리 공간에 많은 프로세스가 적재되므로,

 

개별 프로세스에 할당된 메모리 양이 적어지는 문제가 발생한다.

 

집중적으로 참조되는 페이지 집합을 메모리에 한꺼번에 적재하지 못하기 때문에 페이지 부재가 빈번하게 발생한다.

 

페이지 부재 처리가 진행되면 하드디스크 스왑 영역에서 해당 페이지를 찾아 메모리로 올리는데,

 

이렇게 입출력(I/O) 작업이 진행되는 동안 CPU가 다른 프로세스로 이양되는 문맥 교환(Context Switch)이 발생한다.

 

CPU를 이양받은 다른 프로세스 역시 할당된 메모리가 적기 때문에, 위와 동일한 과정으로 페이지 부재가 발생한다.

 

이처럼 전체적인 페이지 부재율이 증가하면서 CPU이용률은 급감한다.

 

그러면 다시 낮은 CPU 이용률을 높이기 위해 MPD를 상승시키는 악순환이 반복된다.

 

 

 

 

3. 스레싱 방지 기법


1) 워킹셋(Working-set) 알고리즘

 

집중적으로 참조되는 페이지의 집합을 지역성 집합(Locality set)이라고 한다.

 

워킹셋 알고리즘은 지역성 집합이 메모리에 동시에 올라갈 수 있도록 보장하는 메모리 관리 방식이다.

 

원활한 작업을 수행하기 위에 메모리에 한 번에 올라가야 할 페이지 집합을 워킹셋이라고 하는데,

 

워킹셋을 구성하는 페이지가 전체가 메모리에 올라갈 수 있는 프로세스에만 메모리를 할당한다. 

 

메모리에 올라와 있는 워킹셋 크기의 합이 프레임 수보다 클 경우, 

 

일부 프로세스를 스왑 아웃시켜서 남은 프로세스의 워킹셋이 메모리에 올리는 방식으로 MPD를 조절한다.

 

 

2) 페이지 부재 빈도(Page Falut Frequency) 알고리즘

 

프로세스의 페이지 부재율을 주기적으로 조사하고, 이 값을 근거로 프로세스에 할당할 메모리 양을 결정하는 방식이다.

 

페이지 부재율의 상한선하한선을 두어,

 

프로세스의 페이지 부재율이 상한선을 상회하면 더 많은 페이지 프레임을 할당하고,

 

하한선을 하회하면 페이지 프레임 할당량을 감소시킨다. 

 

 

 

 

 

'운영체제' 카테고리의 다른 글

저장 장치의 종류, 계층구조, 캐싱 기법  (0) 2022.04.27

다이나믹 프로그래밍(Dynamic Programming) 알고리즘을 피보나치수열을 예로 들어 설명해보려 한다.

 

해당 문제는 리트코드(LeetCode) 85번 문제를 참조하면 된다.

 

 

< 목차 > 

1. 다이나믹 프로그래밍의 의미

2. 다이나믹프로그래밍의 특성

3. 다이나믹프로그래밍 방법론

4. 다이나믹프로그래밍 의의

 

 

 

 

1. 다이나믹 프로그래밍 의미


다이나믹 프로그래밍(Dynamic Programming)이란 하나의 문제를 여러 개의 하부 문제로 분할하고,

하부 문제를 해결하고 그 결과를 저장하여,

전체 문제 풀이에 더해가며 문제를 해결하는 알고리즘이다.

 

 

 

 

2. 다이나믹 프로그래밍의 특성


다이나믹 프로그래밍으로 해결할 수 있는 문제는 두 가지 특성을 충족해야 한다.

 

1. 부분 최적 구조(Optimal Substructure)

전체 문제의 최적 해결 방법이 부분 문제의 최적 해결 방안으로 구성됨을 의미한다.

 

2. 중복된 하위 문제(Overlapping Sub-problem)

전체 문제를 풀이하는 과정이 여러 번 나타나는 하부 문제 해답으로 구성되는 구조를 말한다.

 

 

피보나치 수열 도식화

 

피보나치수열 예시를 통해 앞서 말한 두 가지 특성을 알아보자.

 

 

피보나치수열은 이전 두 개의 값을 더하여 새로운 값을 구하는 방식이며,

 

수식으로 표현하면 다음과 같다.

 

F(n) = F(n-1) + F(n-2)

 

F(0) = 0이고, F(1) =1일 때, F(2)는 F(1)과 F(2)를 더한 값, 즉 2가 된다.

 

 

피보나치수열은 하부 문제를 해결함으로써 전체 문제에 대한 답을 구할 수 있다.(최적 부분 구조)

 

위에서 말한 수식으로 F(0)부터 계산을 해나가면 그림 상 맨 꼭대기에 있는 F(4)를 구할 수 있다.

 

위 그림에서 F(2)의 경우 꼭대기 F(4)를 기준으로 왼쪽에도 존재하고 오른쪽에도 존재한다.

 

즉 F(4)를 구하기 위해 F(2)가 중복해서 계산이 되는 구조다.(중복된 하위 문제)

 

 

 

 

3. 다이나믹 프로그래밍 방법론


동적 프로그래밍 알고리즘을 이용하여 문제를 해결하는 방법에는 크게 두 가지가 있다. 

 

 

1. 상향식(Bottom-Up)

 

타뷸레이션(tabulation) 이라고도 한다.

 

하위 문제를 먼저 계산한 후, 그 값을 메모리에 저장해둔다.

 

저장된 하위 문제 값을 토대로 전체 문제 값을 계산해나간다.

 

일반적으로 반복적(Iterative) 방법을 이용해서 구현한다.

 

 

2. 하향식(Top-Down)

 

메모이제이션(Memoization)이라고도 부른다.

 

일반적으로 재귀적(Recursive) 방법으로 계산하며,

 

이전에 계산된 하부 문제 값이 존재하는지 참조하며 재귀 호출을 진행하는 방식이다.

 

 

아래는 두 가지 방법으로 피보나치수열을 구현한 코드다.

 

(손으로 지저분하게 작성한 점 양해 바란다)

 

 

(피보나치수열) 타뷸레이션 및 메모이제이션 구현

 

 

 

 

4. 다이나믹 프로그래밍 의의


타뷸레이션과 메모이제이션을 활용하면 미리 저장된 하부 문제의 답을 참조할 수 있기 때문에 연산 과정이 빨라진다.

 

 

피보나치 수열 (좌: 브루트 포스 / 우: 다이나믹 프로그래밍)

 

왼쪽은 브루트 포스 방식으로 구현한 피보나치수열이고, 오른쪽은 다이나믹 프로그래밍(타뷸레이션, 메모이제이션)으로 구현한 그래프다.

 

왼쪽 그림에서는 모든 경우의 수를 다 적용하여 해답(f(4))을 구한 방식을 그래프로 나타냈다.

 

반면 오른쪽 그래프에서  꼭대기를 기준으로 오른쪽 부분을 보면 일부가 잘려 있음을 알 수 있다.

 

f(2)의 경우 꼭대기 기준 왼쪽 부분에서 하부 문제로 해답을 이미 구했기 때문에,

 

오른쪽에서는 f(2)를 다시 쪼개어 정답을 구하지 않고 왼쪽 f(2) 값을 참조한 것이다.

 

 

이처럼 다이나믹 프로그래밍 알고리즘으로 '최적 부분 구조' 및 '중복 하부 문제'의 특성을 지닌 문제를 효율적으로 풀 수 있다. 

 

 

 

 

 

* 본 포스팅은 도서 '파이썬 알고리즘 인터뷰(책만 / 박상길)'을 참조하여 작성했습니다.

CPU 스케줄링이란 어떤 프로세스에게 CPU를 할당할 지 결정하는 방식을 말한다. 다양한 스케줄링 알고리즘 중 대표적인 라운드 로빈 스케줄링에 대해서 살펴보려 한다.

 

 

< 목차 >

1. 라운드 로빈 스케줄링 - 의미

2. 라운드 로빈 스케줄링 - 장점

3. 라운드 로빈 스케줄링 - 단점

4. 라운드 로빈 스케줄링 - 사례

5. 다른 기법과 비교1 - SJF

6. 다른 기법과 비교2 - FCFS

 

 

 

 

1. 라운드 로빈 스케줄링 의미


라운드 로빈 스케줄링(Round Robin Scheduling)은 다른 스케줄링 알고리즘과 달리 시분할 시스템의 성질을 가장 잘 활용한 점이 특징이다. 

 

기본적으로 라운드 로빈 스케줄링은 각 프로세스가 CPU를 연속적으로 사용할 수 있는 시간을 특정 시간으로 제한하는데, 이러한 제한 시간을 할당시간(time quantum)이라고 한다.

 

스케줄러는 사용시간을 초과한 프로세스에게서 CPU를 회수한 다음, 준비 큐에 대기하고 있는 다른 프로세스에게 CPU를 새로 할당한다.

 

이 때, CPU를 회수당한 프로세스는 준비 큐의 맨 뒤에 다시 차례를 기다린다.

 

 

 

 

2. 장점


 

1. 여러 종류의 이질적인 프로세스가 같이 실행되는 환경에서 효과적이다.

 

예를들어 n개의 프로세스가 준비 큐에 대기하고 있고 할당시간이 q라고 가정하면,

 

모든 프로세스는 적어도 (n-1)q 시간 안에는 프로세스를 한번 할당 받는다.

 

선입선출 스케줄링의 경우 먼저 들어온 프로세스의 작업이 오래 끝날때까지 후속 프로세스가 오래 기다리는 경우가 발생한다.

 

이에 비해 라운드 로빈의 경우 할당 시간이 정해져 있기 때문에 비교적 공평하게 CPU 점유 기회를 얻는다.

 

 

2. 대화형 프로세스의 빠른 응답시간을 보장할 수 있다.

 

대화형 프로세스의 경우, 사용자의 입력에 대응하여 즉각적인 출력을 내보내는 것이 중요하다.

 

라운드 로빈은 할당 시간의 제한으로 응답시간(준비 큐에 들어온 시간 ~ 첫 번째 CPU를 획득한 시간)이 짧은 편이기 때문에 사용자에게 즉각적인 피드백을 보내기에 유용하다.

 

 

3. 효율성과 형평성의 균형을 갖췄다.

 

CPU 버스트(사용자 프로그램이 I/O를 수행한 후 다음 I/O를 수행하기까지 CPU를 직접 가지고 명령을 수행하는 일련의 단계)가 짧은 프로세스는 작업을 빨리 마무리할 수 있기 때문에 효율적이다.

 

반면 CPU 버스트가 긴 프로세스도 비교적 빨리 CPU 점유 기회를 얻을 수 있기 때문에 형평성을 갖춘 방식이다.

 

 

 

 

3. 단점


할당시간을 너무 짧게 설정하면 문맥교환의 오버헤드가 증가해 시스템의 효율이 떨어질 수 있다. 

 

예를들어 프로세스 A의 CPU 버스트 시간이 '10'이라고 가정하자.

 

할당시간을 각각 10, 5, 1로 설정할 경우 문맥 교환 발생횟수는 아래와 같다.

 

- 할당시간 10 -> 문맥교환 발생 x

- 할당시간 5 -> 문맥교환 1회 발생

- 할당시간 1 -> 문맥교환 10회 발생

 

할당시간이 10이면 A가 최초로 CPU를 할당 받았을 때 작업을 완료할 수 있다.

 

반면 할당시간이 1이라면 A가 한번에 CPU를 점유할 수 있는 최대 시간이 1이므로, 총 10회의 CPU할당이 필요하다.

 

 

 

4. 사례


프로세스 P1, P2, P3가 순서대로 준비 큐에 도착했고, 할당시간은 10인 경우의 CPU 스케줄링을 생각해보자

 

각 프로세스의 CPU버스트 시간은 아래와 같다

 

프로세스  CPU 버스트 시간
P1 26
P2 13
P3 7

 

 

라운드 로빈 스케줄링을 적용하면 결과는 아래와 같다

 

작업 시간 프로세서
0 ~ 10 P1
10 ~ 20 P2
20 ~ 27 P3
27 ~ 37 P1
37 ~ 40 P2
40 ~ 46 P1

 

P1의 경우 맨 처음 CPU를 할당 받지만 할당시간(10) 내에 작업을 완료하지 못하였고,

 

준비큐 맨 뒤로 가서 대기한 후, 다시 CPU를 할당 받는 과정을 2번 반복했다.

 

 

 

 

5. 다른 기법과 비교 - 최단 작업 우선 스케줄링


1. 최단작업 우선 스케줄링

 

CPU 버스트가 짧은 작업이 CPU 버스트가 긴 선행작업이 마무리 될때까지 오랜 시간 기다리는 현상을 콘보이 현상(Convoy effect)라고 한다.

 

최단 작업 우선(Shortest-Job First:SJF) 스케줄링은 CPU버스트가 가장 짧은 프로세스에 CPU를 우선적으로 할당하는 방식으로 콘보이 현상을 해결한다.

 

하지만 CPU 버스트가 짧은 프로세스가 계속해서 준비 큐에 도착할 경우 CPU 버스트가 긴 프로세스는 무한정 기다리는 문제가 발생한다.

 

즉 효율성을 높이면서 형평성을 희생하는 것이다.

 

 

2. 라운드 로빈 스케줄링

 

라운드 로빈의 경우 보다 공정한 방식으로 스케줄링한다. 

 

프로세스가 CPU를 사용하는 시간에 비례해서 소요시간과 대기시간이 결정되기 때문이다.

 

즉, 할당시간의 제한으로 CPU 버스트가 긴 프로세스도 비교적 짧은 시간 내에 CPU를 점유할 수 있다.

 

 

 

 

6. 다른 기법과 비교 - 선입선출 스케줄링


 

1. 선입선출 스케줄링

 

선입선출(First-Come-First-Served:FCFS) 스케줄링은 준비 큐에 먼저 도착한 순서대로 프로세스에게 CPU를 할당하는 방식이다. 

 

평균 대기시간과 평균 소요시간 측면에서 좋은 결과를 보인다는 장점이 있지만,

 

프로세스 간 대기시간이나 소요 시간의 편차가 매우 크다.

 

단적인 예로 100개의 프로세스가 준비 큐에 대기중이라면 100번 째 프로세스는 앞선 99개의 프로세스가 마무리 될 때까지 대기해야 한다.

 

 

2. 라운드 로빈 스케줄링

 

라운드 로빈 스케줄링은 평균 대기시간과 평균 소요시간이 FCFS 보다 비효율적이다. 

 

하지만, 대기시간이나 처리시간의 편차가 프로세스 마다 크지 않다.

 

100째 프로세스는 앞선 99개 프로세스의 작업이 모두 완료될 때까지 기다릴 필요가 없으며,

 

할당시간이 10이라고 할 경우 99회 x 10 시간만큼 기다린다면 CPU를 할당받을 수 있다. 

 

 

 

 

* 본 포스팅은 저서 '운영체제와 정보기술의 원리(반효경/이화여자대학교출판문화원)'을 참고하여 작성했습니다.

알고리즘 '비트 조작' 파트의 코딩 문제를 풀기 위해

 

꼭 이해해야할 개념인 '부울 연산자', '비트 연산자', '2의 보수'에 관해 알아보고자 한다.

 

 

<목차>

1. 부울 연산자(Boolean Operator)

2. 비트 연산자 (Bitwise Operator)

3. 보수(Complement)

4. 2의 보수 구하기

5. 2의 보수를 이용한 수학 연산

 

 

1.  부울 연산자(Boolean Operator)


 

1.1 개념 

부울(Bool)참(True), 거짓(False)를 나타내는 자료형으로, 

 

부울 연산자는 특정 수들의 논리 연산에 사용되는 기호이다.

 

부울 연산자

 

NOT, AND, OR의 개념은 친숙하지만,

 

XOR은 생소할 수 있겠다.

 

'Exclusive OR'이라고 읽으며, 

 

값이 서로 다를 때(T-F or F-T)만 참(True)이되는 연산자다.

 

 

1.2 예시코드

부울 연산자 예시 코드

 

 

 

2. 비트 연산자(Bitwise Operator)


 

2.1 기호

부울 연산자를 비트 연산자로 표현할 수 있다.

 

비트 연산자

 

2.2 예시 코드

비트 연산자 코드 예시

위 사례에서 주목할 부분은 비트 연산자 NOT(~, 틸트)이다.

 

틸트를 부울 변수 False(=0)에 적용할 때  -1이 출력된다.

 

연산자 NOT은 2의 보수에서 1을 뺀 값과 같기 때문이다.

 

그러면 2의 보수는 무엇일까?

 

 

 

 

3. 보수(Complement) 


3.1 개념

2의 보수에 앞서, 보수의 기본 개념에 대해서 살펴보자.

 

보수는 컴퓨터가 음수를 계산하기 위해 취하는 방법의 일종이다.

 

컴퓨터는 가산기를 이용하여 덧셈, 뺄셈, 곱셈, 나눗셈을 계산한다.

 

즉, 덧셈만 이용하여 뺄셈, 곱셈, 나눗셈을 계산해야하는 것이다.

 

곱셈은 덧셈을, 나눗셈은 뺄셈을 반복하면 된다.

 

그럼 덧셈을 이용하여 뺄셈은 어떻게 계산할까?

 

이때 등장하는 개념이 '보수'다.

 

 

3.2 예시 

보수를 활용한 뺄셈 연산 사례

컴퓨터가 '6 - 2 = 4' 를 계산해야 한다고 하자.

 

하지만 컴퓨터는 '-2'를 직접 계산할 수 없기 때문에, 

 

덧셈을 이용해서 뺄셈(-2)을 연산해야 한다.

 

'-2'의 (10의)보수는 '-2'에서 '+10'만큼 이동한 '+8'이다.

 

'6'과 '-4'의 보수인 '+8'을 더한 값(6 + 8)은 '14'다.

 

'14'에서 십의 자리수 '1'을 제외하고 '4'만 출력한다.

 

그럼 본래 (6-2 의)답 '4'와 동일한 결과를 얻을 수 있다.

 

 

 

 

4. 2의 보수 구하기


 

4.2.1 사례 

-14의 (2의)보수 구하기

십진수 -14의 보수를 구하려고 한다.

 

십진수를 이진수로 바꾸면 -1110이 된다.

 

-1000을 반전시키면 1의 보수(+0001)을 구할 수 있다.

(편의상 반전이라 칭했으나, 실제로는 마스크 값(1111)와 XOR 연산한 값이다.)

1의 보수에 1을 더하면 2의 보수를 구할 수 있다. 

 

 

 

 

5. 2의 보수를 이용한 연산

 

5.2.1 사례


보수를 이용하여 '14 - 10 = 4' 계산하기

 

컴퓨터가 '14 - 10 = 4'을 계산한다고 하자.

 

거듭 말했듯이 컴퓨터는 음수(-10)을 직접 계산할 수 없기에, 보수를 활용한다.

 

우선 '14 - 10 = 4'을 이진수로 나타내면, '1110 - 1010 = 0100'이 된다. 

 

'-1010'의 2의 보수를 나타내기 위해, 

 

우선 반전을 통해 1의 보수(+0101)을 구한 다음,

 

1을 더해주면 2의 보수(+0110)를 구할 수 있다. 

 

-10의 2의 보수를 구한 후, 본래 식을 계산하면 '10100'이 나온다.

 

맨 앞에 1(자릿수 올림을 통해 새로 생겨난 값으로 '캐리'라고 한다)을 버리면 '0100'으로,

 

2의 보수를 구하기 전의 결과 값과 동일하다. 

 

 

 

* 본 포스팅은 도서 '파이썬 알고리즘 인터뷰' 및 유튜브 '대멀쌤의 자료를 참고했습니다.(https://www.youtube.com/c/%EB%8C%80%EB%A9%80%EC%8C%A4)

 

 

 

클래스를 정의할 때,

 

항상 첫 번째 함수는 초기화 함수로 시작하곤 했다.

 

사실 __init__ 메서드를 

 

왜 쓰는지 모르고 그냥 써왔다. 

 

초기화 함수를 지정하지 않고 class를 정의 해본다면

 

초기화 함수의 역할을 가장 간단히 이해할 수 있다.

 

1. 초기화 함수를 사용하지 않고 class를 정의

 

숫자 1~5까지 리스트 샘플을 만드는 클래스 내에,

 

초기화 함수 없이 숫자를 추가하고, 리스트를 출력하는 함수를 생성했다.

 

 

클래스 객체를 생성하고 숫자 6을 추가한 후, 해당 리스트를 출력하면

 

[1, 2, 3, 4, 5, 6]이 출력된다.

 

 

다시 클래스 객체를 생성하고 이번에 숫자 7을 추가한 후, 리스트를 출력하면

 

[1, 2, 3, 4, 5, 7]이 아니라 [1, 2, 3, 4, 5, 6, 7]이 출력된다.

 

클래스를 새로 생성하더라도, 초기값 리스트(1~5까지 숫자)가 아니라,

 

직전에 만들어진 객체를 그대로 받아서 쓴다.

 

 

 

2. __init__ 메서드를 사용하여 class를 정의

 

반면에 __init__ 메서드를 생성한 후,

 

클래스 객체를 새로 생성하면,

 

새로 생성된 객체는 초기화 되어 있는 사실을 알 수 있다. 

 

 

즉, 이전에 생성한 클래스 객체에 숫자 6을 추가했더라도,

 

클래스 객체를 새로 생성하면 숫자 6은 사라지고, 초기값(1~5)만 남아 있는 것이다.

 

 

 

 

 

 

 

 

+ Recent posts