supergravity

파이썬 - 메모리 관리와 좋은 습관 본문

콘텐츠/파이썬 -구조

파이썬 - 메모리 관리와 좋은 습관

supergravity 2021. 8. 31. 15:38

1. 시작

파이썬은 메모리 관리를 자동으로 해주는 언어입니다.

그러면 우리는 왜???

파이썬 메모리 관리에 대해서 배워야 하는 것일까요?

파이썬에서는 효율적인 메모리 관리를 위해 수만 줄의 코드를 사용하여 최적화해놓았습니다.

하지만 파이썬 최적화 코드에 대한 지식이 없다면 안 좋은 코드 습관이 생기게 됩니다.

이러한 무지성으로 생성된 코드 습관은 파이썬 고수들이 작성한 수만 줄의 코드를 냅다 버리게 됩니다.

그러면 파이썬의 메모리 관리가 어떻게 구현되는지 알아보고 더 나아가 좋은 코드 습관은 무엇인지 알아봅시다.

2. 파이썬은 어떻게 실행되나?

가장 먼저 파이썬이 어떻게 작동하는지 큰 그림을 그려 봅시다.

파이썬은 인터프린터 언어라 C처럼 컴파일을 하지 않고 한줄한줄 읽는다고 생각할 수도 있지만!

고수가 되기 위해서는 거기서 끝나면 안 됩니다.

파이썬은 파이썬 코드를 컴퓨터의 언어인 0과 1로 구성된 파일로 변환시키는 컴파일링과

한 줄 한 줄 읽으며 해석하는 인터프린팅을 혼합하여 사용하고 있습니다.

우리가 코드를 작성하고 실행 버튼을 누르게 되면

파이썬 코드는  컴퓨터의 언어인 0과 1로 구성된 바이트코드로 변하게  됩니다.

이러한 과정이 일어나는 시간을 컴파일타임이라 합니다.

변환된 바이트 코드는 메모리에 올라가 차례차례 실행이 됩니다.

이 과정을 인터 프린팅 혹은 런타임이라고 합니다.

런타임 때 파이썬은 바이트코드를 python virtual mechine라는 가상 컴퓨터에서 실행을 하게 됩니다.

외부 모듈의 경의 경우 가상 머신이 만들어지고 바이트 코드를 해석할 때 추가됩니다.

 

Note

여기서 가상환경이란? 윈도우스 위에 다시 mac을 설치하는 느낌입니다.

파이썬은 사용자 OS에 python virtual mechine이라는 가상환경을 만듭니다.

 

 

위와 같은 일을 하는 프로그램들은 많이 있습니다.

예를 들면 cpython, pypy, ironpython.... 엄청 많습니다.

이들을 파이썬 인터프린터 라고 합니다.

이들 중 파이썬 설치 시 기본적으로 제공하는 인터프린터는 cpython입니다.

cpython은 기본적으로 제공이 되고 있을 뿐만 아니라 가장 많이 사용하고 있기 때문에 이에 대해 집중적으로 알아봅시다.

3. cpython 메모리 관리

파이썬 실행 시 컴파일 타임과 런타임 때 메모리 관리가 어떻게 진행이 되는지 알아보기 위해

우선적으로  일반적인 메모리 관리에 대해서 알아야 합니다.

  

컴퓨터 공학에서 일반적인 메모리 관리

 

메모리 관리 중 가장 가장 중요하고 많이 다루는 부분은 메모리 할당입니다.

메모리 할당에는 정적 메모리 할당과 동적 메모리 할당으로 분류되어 사용하고 있습니다.

그러면 하나씩 알아봅시다

 

정적 메모리 할당 -

코드들이 컴파일 시간에  할당되는 것을 말합니다.

정적 메모리를 구현하기 위해서 stack자료구조를 사용합니다.

프로그램에 사용할 데이터를 stack에 저장해 두었다가 하나씩 꺼내어 사용합니다.

프로그램이 끝날 때까지 없어지지 않는 특징을 가지고 있습니다.

 

Note 

stack자료 구조의 경우 데이터를 저장할 때는 차곡차곡 쌓아 올립니다.

사용할 때는 위에서부터 사용합니다.

중간에 꺼 사용하면 안 되냐고 생각하시는 분들이 있는데

구현에 따라 가능하겠지만..

그냥 위에서 빼서 사용하겠다고 정의해 놓은 것입니다.

 

동적 메모리 할당 -

코드가 실행이 되고 나서 메모리에 할당되는 것을 말합니다. 

동적 메모리를 구현하기 위해서는 heap자료구조를 이용합니다.

정적과 달리 데이터가 나중에 필요하지 않으면 메모리에서 제거를 할 수 있습니다.

 

Note

heap은 조금 어려워 독립된 글을 만들어 설명을 해야 합니다ㅠㅠ

필요하시면 클릭 !

파이썬에서는 정적 메모리에 정의된 함수와 변수의 이름들이 들어가며 

힙 영역에는 모든 오브젝트 들이 들어갑니다.

이 모든 정보는 cptyhon에 구현되어 있습니다.

cptyhon c로 구현된 언어이고 파이썬을  OS에 실행하는데 필요한 프로그램입니다.

 

그러면 예를 통해 Cpython이 어떻게 작동하는지 감을 잡아 봅시다.

 

4 cpython 기본적인 메모리 관리 예제

파이썬 인터프린터는 실행 버튼을 누르게 되면 기본적인 세팅을 합니다.

그리고 위에서 아래로 한 줄씩 읽으면서 코드를 이해합니다.

그러면 파이썬 코드가 실행되었을 때 메모리가 어떻게 되는지 확인해 봅시다.

 

이번 예제의 경우 전적으로 컴파일 시간에 object들이 메모리에 올려지게 됩니다.

런타임 때 이루어지는 경우는 함수를 사용할 때 등장합니다.

 

Note

object는 메모리에 존재하는 실제 값을 말합니다.

object의 정의는 클래스의 인스턴스입니다. 결국 추상적인 클래스의 구체적인 예제가 object입니다.

object에 대해서 잘 모른다면클릭! 

 

Cpython에서는 레퍼런스를 메모리 주소 값을 이용하여 구현하고 있습니다.

그래서  a = 3을 만나면 변수의 레퍼런스와 오브잭트를 생성하여 스택과 힙에 올려놓습니다.

 

Note

여기서 레퍼런스란?

3에 값에 접근할 수 있는 이름 a를 말합니다.

b = 5을 만나면 변수 b의 레퍼런스와 인스턴스(object)를 생성해 스택과 힙에 올려놓습니다.

a = 5를 만나면 a의 레퍼런스가 이미 존재하기 때문에 레퍼런스 값만 오브잭트 5의 주소 값으로 변경합니다.

c =5를 만나면 c의 레퍼런스를 생성하고 스택에 올립니다.

오브잭트 5의 값은 이미 존재하기 때문에 c의 레퍼런스 값을 오브잭트 5의 주소 값으로 저장합니다.

 

여기까지 진행된 메모리를 보면 오브잭트 5에는 2개의 레퍼런스가 존재하고 있고 오브잭트 3에는 0개의 레퍼런스가 존재합니다.

여기서 3이라는 오브잭트를 굳이 남겨놓을 필요가 없습니다.

그래서 파이썬은 오브잭트의 레퍼런스가 0인 오브잭트를 삭제하여 메모리 낭비를 줄입니다.

5. mutable VS immutable

파이썬에서는 object의 종류에 따라 메모리를 관리하는 방법이 달라집니다.

데이터 타입에 따라  디테일하게는 모두 다르긴 합니다.

하지만  mutable과 immutable로 분류해 생각해 봅시다.

 

아까 전에 예제의 경우 immutable 타입인 정수형에 대해서 살펴본 것입니다.

int, float, bool, str, tuple, unicode, bytes의 경우 immuatable 오브잭트입니다.

이 말은 "메모리에 올라간 정수형 오브젝트는 변경이 불가능하다"라는 뜻을 가지고 있습니다.

그래서 메모리에서는 object는 변하지 않고 reference만 변합니다.

이를 전문용어로 interning이라고 합니다.

 

대조적으로 list, bytearray, collection.deque, dictionary 경우 mutable타입입니다.

이 말은 "오브잭트의 메모리 주소 값만 알면 변경이 가능하다"라는 뜻을 가지고 있습니다.

 

구체화를 위해 예를 살펴봅시다.

 

첫 번째 예제의 경우 IMMUTABLE 정수형 예제입니다.

결과를 보면 ID가 변화된 것을 확인할 수 있습니다.

두 번째는 튜플형입니다.

튜플도 정수형과 마찬가지로 IMMUTABLE입니다.

결과를 보면 ID가 변화된 것을 확인할 수 있습니다.

세 번째는 리스트 입니다.

리스트의 경우 MUTANLE입니다.

결과를 보면 id가 같은 것을 확인할 수 있습니다.

보는 바와 같이  리스트의 경우 오브잭트의 위치를 알면 어디 에서나  값을 변경할 수 있지만. 

정수형의 경우 변경이 불가능한 것을 볼 수 있습니다.

이러한 특징은 파이썬 메모리 관리에 영향을 줍니다.

6. 컴파일 시 저장되는 것들 VS 런타임 시 저장되는 것들

컴파일 타임에 저장되는 것과 런타임에 저장되는 차이점을 익이히 위한 코드를 봅시다.

ex_1은 "darkPhyhon"과 "dark" + "Python"의 메모리 위치가 같은지를 확인하는 코드입니다.

파이썬에서 is 문법은 비교하는 두 object가 같은지 확인하는 코드입니다.

두 object의 같음을 확인하기 위해서 저장되어 있는 id(혹은 메모리 주소)를 비교합니다. 

첫 번째의 경우 오브잭트의 위치가 같습니다.

하지만 두 번째의 경우 위치가 다릅니다.

무슨 일이 일어난 것일까?

이를 알아보기 위해서 dis 모듈을 이용하여 자세히 들여다볼 수 있습니다.

dis 모듈은 바이트 코드를 보기 좋게 프린트해주는 모듈입니다.

 

그러면 출력된 바이트 코드를 비교를 해봅시다.

예제 1의 경우와 2의 가장 큰 차이점은 BINARY_ADD입니다.

예제 1은 스트링 연산이 없습니다.

예제 2의 경우에는 스트링 연산이 들어있습니다.

파이썬은 cpythond을 이용하여 컴피 일하고 이를 다시 인터 프린팅 한다고 했습니다.

예제 1번의 경우 파이썬 고수들이 작성한 코드를 이용되었습니다.

컴파일 과정에서 파이썬 오브잭트들을 효율적으로 힙 메모리에 배열한 상태입니다.

하지만 예제 2의 경우에는 로컬 변수가 계산을 해야 하는 대상이 되어 버렸습니다.

그래서 BINARY_ADD가 실행되었고 이는 런타임 과정에서 진행이 됩니다.

 

근데 왜? 계산해야 하는 대상이 되었을까요?

로컬변수의 경우 함수가 실행될 때 생성되고 종료될 때 삭제됩니다.

하지만 함수의 실행에 관한 부분은 런타임 과정에서 처리를 합니다.

 

Note

이에 대해서 자세히 알고 싶으면. 클릭!

 

BINARY_ADD연산이 런타임에 진행된다는 말은..

파이썬 고수들의 최적화 코드를 사용하지 못한다는 말입니다.

그래서 예제 1번처럼 코드를 작성해야 합니다.

7. 좋은 습관 만들기

이전에 스트링 예제를 본 거와 같이 Cpython의 알고리즘에 따라 효율적인 코딩 방식이 정해집니다.

cpython을 공부하면 좋겠지만 코드도 많고 조따 빡셉니다.

하지만 걱정할 필요가 없습니다.

나만 힘들고 우리만 힘든 게 아니기 때문에 인생을 대충 살기 위해 선배님들이 정리를 해놓았습니다.

구글링을 하면 다양한 글들에서 효율적으로 작성하는 방법에 대한 글들이 있습니다.

여기서 중요한 습관들만 골라서 익히도록 합시다.

7.1 제너레이터를 사용해라

제너레이터는 아이템을 한 번에 리턴하는 것이 아니라 yield를 통해 하나씩 리턴해줍니다.

이 말은 조따큰 리스트를 검색할 때까지 기다리지 않는다는 말이라서 큰 리스트를 다룰 때 유용하게 쓰일 수 있습니다.

https://www.freecodecamp.org/news/how-and-why-you-should-use-python-generators-f6fb56650888/

https://en.wikipedia.org/wiki/Lazy_evaluation

def silver():
    a = [1, 2, 3, 4]
    b = []
    for ele in a:
        b.append(ele*2)
    return b

def gold():
    a = [1, 2, 3, 4]
    for ele in a:
        yield ele*2

특히 실버의 코드의 경우에는 리스트 a와 b를 동시에 저장함으로 메모리를 골드보다 2배 더 많이 차지합니다.

7.2 로컬 함수에 다시 선언하기

파이썬은 글로벌 보다 로컬 변수가 조따게 빠릅니다.

그래서 함수를 로컬에 다시 정의하면 빠르게 사용할 수 있다.

빠른 이유를 알고 싶다면 클릭!

def silver():
    print("나는 귀여운 실버다!")

def gold():
    pri = print
    pri("하하 나는 골드다!")

7.3 구현되어 있는 함수나 라이브러리를 사용해라

파이썬 고수들이 만들어 놓은 걸 사용하면 중간은 합니다.

무지성으로 창작하는 것도 연습상 좋지만 구현되어 있는 라이브러리를 찾는 습관이 병행된다면 진짜 좋은 습관입니다.

무지성:

mylist=[]
for myword in oldlist:
    mylist.append(myword.upper())

욕 안먹을 정도:

mylist=map(str.lower, oldlist)

7.4 itertools 이용하기

이터툴을 사용하면 루프의 시간을 줄일 수도 있지만 읽기도 편합니다.

남이쓴 루프 2개만 봐도 짜증이 치밀어 오른다. 하지말자 그래도 급할땐? 그래도 하지말자:

mylist=[]
for shape in [True, False]:
    for weight in (1, 5):
        firstlist=firstlist+function(shape, weight)

굿:

from itertools import product, chain
list(chain.from_iterable(function(shape, weight) for weight, 
shape in product([True, False], range(1, 5))))

8 결론

파이썬은 많은 사람들이 사용하는 오픈소스입니다.

그래서 다양한 인터프리터가 존재할 뿐만 아니라 빠르게 업데이트 됩니다.

그렇지만 3가지의 코드 습관을 가지고 있다면 잘 적응하고 사용할수 있습니다.

 

1. 파이썬 인터프리터가 어떻게 작동할지 생각하는 습관 

2. 구현된 것들 찾아보는 습관

3. 컴파일타임에 작동을하는 코드인지 런타임에 작동하는 코드인지 생각하는 습관

 

 

 

 

'콘텐츠 > 파이썬 -구조' 카테고리의 다른 글

파이썬 - __main__  (0) 2021.09.08
파이썬 - namespaces, scopes  (0) 2021.09.06
cython , cpython, python ?  (0) 2021.09.04
파이썬 - 스트링을 메모리에 어떻게 저장할까?  (2) 2021.08.29
Comments