도니의 iOS 프로그래밍 세상

[Swift] COW(Copy On Write) - 메모리 절약 방법 - Structure 응용편, OS응용편 본문

Swift

[Swift] COW(Copy On Write) - 메모리 절약 방법 - Structure 응용편, OS응용편

Donee 2023. 1. 2. 00:09

이전 포스팅에서는 Structure와 Class의 차이에 대해서 공부했습니다.

그때 Value Type인 Structure에서 반드시 값의 복사를 통해서 값을 전달한다고 했지만, 반드시 그런것은 아닙니다!

Swift의 Structure의 추가 설명에서

Collection Type인 array, dictionary, String은 복사로 인한 퍼포먼스 코스트를 줄이기위해 최적화된 별도의 방식을 사용합니다

라고합니다. 

그것이 오늘 소개할 COW(Copy On Write)입니다!!

Swift에서의 COW(Copy On Write)

스위프트 문서상의 설명으로는

복사를 즉각적으로 하지 않고, collection들은 값들이 저장된 메모리를 원본 객체와 복사본들끼리 공유합니다. 만약, collection의 복사본들중 하나가 변경되었다면, 수정되기 전에 복사됩니다.

쉽게 말해서, 두 객체간에 바로 값을 복사하지 않고, 서로 공유하고 있다가 만약 한 객체가 값을 변경하면 그때서야 서로 분리되는 것입니다.
예제를 보면서 이해할께요!

Copy On Write의 예시

var origin = [1, 2, 3]
var copy1 = origin

Int는 Value Type이기 때문에 Int Array또한 Value Type입니다.
이전 포스팅에서 배운대로라면 Value Type은 값의 전달을 "복사"를 기본으로 이루어지게 됩니다.

왼쪽 그림은 값이 복사돼서 별도로 공간이 확보, 오른쪽 그림은 값이 복사되지 않아 공간을 공유

그리하여 왼쪽 그림과 같은 형태로 이루어져야 합니다.

하지만 실제론 어떻게 될까요?

오른쪽 그림과 같이, 값이 바로 복사되지 않고 하나의 공간을 서로 가르키고 있습니다.
결국 Swift 문서처럼 Collection들은 실제로 값이 바로 복사되지 않고 같은 공간을 share하다가, 변경이 일어났을 때 바꾸게 됩니다.
실제로 그런지 코드를 통해서 확인해볼게요!

Copy On Write 코드 예시

var origin = [1, 2, 3]
var copy1 = origin
print("origin address: ",address(of: origin), "copy1 address: ",address(of: copy1))

origin[0] = 1
print("origin address: ",address(of: origin), "copy1 address: ",address(of: copy1))

앞서 배운 지식을 근거로, 첫번째 프린트문에서는 동일한 주솟값이 나와야 하고, 두번째 프린트문에서는 다른 주솟값이 나와야합니다.
Collection Type들은 값을 바로 복사하지 않고 공유하다가, 값의 변경 직전에 Copy되어 두개가 모두 분리되니까요!

origin address:  0x6000028f56e0 copy1 address:  0x6000028f56e0
origin address:  0x6000028c1520 copy1 address:  0x6000028f56e0

실제 결과값을 볼게요~!
결과적으로 저희의 추측이 맞았네요!
Swift보다 근본적인 OS Layer Copy On Write를 한 이유에 대해서 알아볼게요. 여기까지만 알아도 충분하십니다!

OS에서 Copy On Write(메모리를 절약하기 위한 방법)

Copy On Write는 OS에서 더 많이 쓰인 표현입니다.
부모 프로세스가 자식 프로세스를 fork할 때, 기본적으로 부모 프로세스의 address space를 자식 프로세스의 address space로 copy해서 완전히 동일합니다.
이때 process 를 create를 해서 자식 process의 address space공간이 만들어지고, 그 공간에 부모 process의 address space를 memory copy로 전부 카피하는것은 굉장히 큰 Overhead가 발생합니다.
그렇다면 실제로 어떻게 하고 있을까요?

Virtual Memory, Page Table, Page

실제로 어떻게 발생하는지 이해하기 위해서는 OS에서 사용하는 "Virtual Memory, Page Table, Page"에 대한 개념을 어느정도 숙지하고 계셔야 합니다.

Virtual Memory

cpu가 메모리에 접근할 때, 현재 수행하고 있는 코드와 그에 따른 필요한 데이터를 접근합니다.
결국 cpu 입장에서는 자신이 실행해야할 instruction과 접근 해야할 데이터만 memory에 올라와있다면 문제가 없습니다.
그리하여 이론상으론 "모든 프로세스의 address space 메모리에 올라와있는 상태에서 프로그램이 실행되어야 한다" 라는 가정을 하고 있습니다.
하지만,실질적으로 cpu가 접근하는 부분은 현재실행하는 코드와 코드와 연관된 데이터로 한정됩니다.
따라서 cpu가 접근하는 부분만 있으면 되고, 그것이 실제로 문제가 없다면 동시에 실행할 수 있는 프로세스의 수가 증가합니다.
(만약 메모리가 32GB이고 하나의 프로세스 용량이 16GB라면? 두가지 프로그램 밖에 메모리에 올리지 못합니다.
하지만 실제로 사용하는 데이터들은 순간적으로 최대 1GB를 넘지 않는다면 어떻게 될까요? 32개의 프로세스를 올리는 것이 가능합니다.)
그리하여 메모리 활용성을 극적으로 높이기 위함이 바로 virtual memory라고 이해하시면 될 것 같습니다.

Page, Page Table

Process(프로그램이 동적인 상태에 있을때를 의미)는 address space를 가지고 이는 memory에 올라와 있습니다.
address space는 Page단위로 쪼개지며 이들은 비연속적인 구조를 지닙니다.
address space가 비연속적으로 이루어져있기 때문에 page Table이란 것을 가지고, 이를 통해서 mapping되어 있는 구조입니다.
결국 page Table은 page들을 가르키고 있으며, page Table은 logical Address로서 실제로 메모리에 없을수 있는 것들도 포함합니다.
page는 실제 physical memory에 존재하는 데이터들입니다.
분량상 한계로 paging 기법은 여기서 소개하지 않겠습니다.

Copy On Write in OS

결국 Virtual memory를 각자의 프로세스는 가지고 있으며, page table은 page를 가르키고 있습니다.
아까의 주제로 돌아와서, fork시 자식 프로세스는 반드시 부모 프로세스를 그대로 copy해야 할까요?
부모 process와 동일한 address space를 자식 process가 사용할 수 있도록 page table을 동일하게 만들어줍니다.
page table이 copy되기 때문에 그렇게 하면 memory copy overhead가 엄청나게 줄어들게 됩니다.

Copy가 이루어지는 상황

나중에 address space의 부모나 자식 둘중 아무나 데이터를 write했다면 어떻게 될까요?
자식 process가 exec 시스템 call 을 이용해서 address space를 바꾸려고 한다면 바꿔줘야 합니다.
또는 자식 프로세스가 부모하고 똑같은 일을 해서 address space를 갈아 엎을 필요가 없더라도 부모가 자신의 address space에 write하면 phyiscal memory에 write하면 자식까지 영향을 미치게 됩니다.
누군가가 공유하고 있는 page에 write를 하면 그제서야 copy를 만들어서 그 copy본에다가 write를 하게 되기에 "Copy on Write"라는 말이 붙게 되었습니다.

Copy on Write의 예시

왼쪽 그림은 page C를 변경하기 전 process1과 process2의 상황입니다.
오른쪽 그림은 page C를 변경한 후 process1과 process2의 상황입니다.

두 그림에서 보듯, 동일한 address space를 갖기 위해 page Table이 동일한 page를 가르키도록 복사되었습니다.
그리고 만약 page C를 누가 변경하게 된다면, "필요한 page"만 복사되고 나머지들은 모두 공유됩니다.
결국 copy on write기법을 통해서 최대한 많은 자원들을 공유하고 공유가 되지 않는 경우만 분리를 시킴으로써 메모리 효율을 극대화 시킵니다.

출처

https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
Operating System Concepts Tenth Edition. Avi Silberschatz · Peter Baer Galvin · Greg Gagne.
"운영체제", 한양대학교 컴퓨터소프트웨어학부(2021년 자료)

Comments