> For the complete documentation index, see [llms.txt](https://c-auto.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://c-auto.gitbook.io/blog/python/memory.md).

# Python은 자동으로 메모리 관리를 자동으로 해주는데, 몰라도 괜찮을까?

일반적으로 파이썬으로 개발을하다 보면, JAVA, C 언어와 달리 메모리 관리를 전혀 고민을 해보지 않거나, 개념조차 모르는 경우가 많습니다. 물론 Python은 `python memory management`을 통해 자동으로 관리되지만, 빅데이터 사용(ML등)이 증가함에 따라 메모리 관리의 중요성은 파이썬에서도 중요한 문제 중 하나 입니다. 특히, 비효울적인 메모리 관리로 인해 프로그램 및 서버 구성요소가 느려지거나, 메모리 누수 등으로 테스트 및 디버깅에 많은 시간을 낭비하며, 데이터 동시 처리 문제 등에도 영향을 줄 수 있습니다. 이번 글은 메모리에대한 이해와 파이썬 메모리 할당을 이해하고 보다 효율적인 메모리 사용을 통한 파이썬 코드 작성에대해 고민합니다.

<br>

## #1. 메모리 구조 📝

메모리 구조의 경우 아마 대부분의 신입 개발자 CS 질문을 통해 아래와 같은 그림을 한 번쯤은 본적이 있을 것입니다.

<img src="https://github.com/C-auto/share-docs/assets/91866763/740f13d5-9305-474c-95c0-8b538593a1dc" alt="" width="600">

아래부터 설명드리면, Text 영역은 실행할 프로그램의 코드가 저장됩니다. Text 영역은 정적 영역으로 프로그램에 내장되어 있는 소스 코드가 들어가있으며, CPU는 텍스트 영역에 저장된 명령어(코드)를 하나씩 가져가면서 처리합니다. 이어서 데이터 영역(Data + Bss)은 \*전역변수, \*정적변수가 저장되며 정적인 특징을 가진 프로그램의 시작과 함께 할당되며, 프로그램이 종료되면 자동으로 소멸됩니다. 데이터 영역 중 BSS 영역에는 초기화가 되지 않은 변수가 0으로 초기화되어 저장되어지며, Data 영역은 0이 아닌 다른 값으로 할당된 변수가 저장됩니다. Heap 영역은 사용자의 동적 할당으로 생성되는 공간으로 런타임 시 그 크기가 결정되는 동적인 특징을 가집니다. Heap 영역은 사용자가 공간의 크기를 직접 관리할 수 있는 특징을 가지고 있지만, 파이썬의 경우 동적 할당을 해주는 기능없이 해당 역할을 `python memory management`을 통해 관리됩니다. 마지막으로 Stack 영역은 지역변수와 매개변수를 저장하는 공간으로 컴파일 시 그 크기가 결정되는 동적인 특징을 가집니다. 함수를 호출할 때마다 함께 Stack 영역에 메모리를 할당하며 함수 호출이 완료되면 소멸합니다.

\*전역변수(Global variable) : 어디서든지 참조할 수 있는 변수로, 프로그램이 종료 전까지 메모리가 소멸되지 않는 특징을 가진 변수

\*정적변수(Static variable) : 전역변수와 마찬가지로 프로그램 종료 전까지 메모리가 소멸되지 않는 변수로 해당 변수가 선언된 scope에 따라 접근 가능한 범위가 정해지는 변수

<br>

## #2. 파이썬 메모리 할당

파이썬 메모리 할당을 설명하기위해서는 "파이썬은 모든 것이 객체다."(Everything is object in Python)는 말의 의미를 이해할 필요가 있습니다.

```python
x = 1
print(type(x))

>> <class 'int'>
```

일반적으로 C언에서 `x = 1`처럼 변수를 할당하면 메모리에 해당 값 `1`이 저장되지만, 파이썬에서는 int라는 object를 만들어서 변수 x가 그 object를 가리키는 형태입니다. x의 타입을 추출하면 class int를 출력값으로 볼 수 있는데 이 클래스를 구체화한 것이 Object 입니다.

\*참고 : class 는 추상적이며, object는 구체적인 것으로 파이썬에서 존재하는 타입은 class로 정의 됩니다. 즉, 파이썬으로 우리가 list를 만들 때는 사실 list라는 class를 사용해서 object를 만드는 개념입니다.

```python
x = 1
y = x
if (id(x) == id(y)):
  print("x, y is same object")

>> x, y is same object
```

그러면 여기서 이미 만들어진 1인 int object를 가르키는 x를 `y = x`로 하면, x와 y는 같은 1인 int object를 가리키는 같은 id를 가짐을 확인할 수 있습니다. 즉, 여기서 설명드리고 싶은 부분은 파이썬은 모드 객체이며 메모리 할당 시 해당 값을 직접 할당하는 것이 아니라. 값을 객체로 만들고 변수가 이를 참조하는 방식으로 memory를 할당하고 있음을 알 필요가 있습니다.

<br>

## #3. 파이썬 메모리 관리

위에서 파이썬은 객체가 메모리에 할당되는 것은 확인했는데 앞서 설명한 메모리 구조 중 어느 영역을 사용하는 가를 고민해야합니다. C, Java의 경우 malloc 함수를 이용해서 동적 할당 기능을 제공하고 있지만 파이썬은 동적 할당 기능이 없습니다. 이는 즉, 파이썬이 사용자가 직접 메모리 할당을 조절하지 않고 자동으로 메모리를 관리해주는 언어임을 나타냅니다. 파이썬의 Python Memory Manager의 Python/C API를 통해 포인터를 움직여 Heap 영역의 메모리 할당 범위와 내부 버퍼를 조정해줍니다. 즉, 메모리 공유, 메모리 세분화, 메모리 사전 할당, 캐싱 등 메모리 동적 관리가 필요할 때 Heap 안에 메모리 영역을 Interpreter가 포인터를 사용해 영역의 범위를 조정함으로써, 자동으로 메모리를 OS에 할당하지 않고 있다가, 사용될 때 메모리를 재사용하는 방식으로 운영됩니다. 간단하게 정리하면, 변수나 함수가 호출됨에 따라 그에 맞는 메모리를 Python Memory Manager가 OS와 소통하면서 할당하고 결과 값이 return 되거나 변수와 함수 사용이 멈추었을 때, 메모리가 소멸되는 방식입니다.

파이썬 코드로 설명드리면 아래와 같습니다.

```python
def f2(x):
    x = x + 1
    return x
    
def f1(x):
    x = x * 2
    y = f2(x)
    return y

#main
y = 5
z = f1(y)
```

위와 같은 코드가 실행되었다가 가정할 때 메모리는 다음과 같이 할당 됩니다.

* stack memory
  * f2 함수의 매개변수 x
  * f1 함수의 매개변수 x, 지역변수 y
  * main의 지역변수 y, z
* heap memory(Heap 메모리에 저장된 값을 변수가 가리킵니다.)
  * f2의 x, f1의 y -> 11
  * f1의 x -> 10
  * main의 y -> 5

할당의 해제는 Stack의 가장 위에 있는 f2 함수부터 해제됩니다. f2 함수 해제 후 f1 함수가 해제되는 f1의 x 변수가 가르키는 int object 10를 가르키는 변수가 없기 때문에 \*reference counting 0이 됨에 따라 GC에 의해 사라집니다. 마지막으로 main 함수의 변수 z가 11을 가르킴에따라 프로그램 실행이 마무리됩니다.

참조 : <https://woochan-autobiography.tistory.com/867>

\*reference count : 파이썬의 모든 객체를 카운팅 하는 것을 의미. 객체가 참조될 때 증가하고, 참조가 삭제되면 감소시키는 방식으로 동작. referece count가 0이되면 삭제 대상이 되며 삭제 cycle에 의해 메모리 할당이 해제됨.

<br>

## #4. 효율적인 파이썬 코드 작성

위의 내용을 정리하면, 매서드와 변수는 스택 메모리에 작성되며, 스택 메모리는 해당 매서드가 리턴 될때마다 자동으로 제거됩니다. 그리고 객체(Object)와 인스턴스 변수(객체에서 정의된 변수)는 힙메모리에 저장되며 힙 메모리는 가비지컬렉터(GC)에 의해 reference counting을 통해 제거됩니다. 사실 파이썬의 경우 메모리 관리를 조정할 수 있는 부분이 거의 없어 일반적으로 메모리 관리를 직접적으로 사용해서 성능을 높이는 방법이 거의(?) 없습니다. 다만, 파이썬에서의 값들은 모두 객체로 저장이되면 해당 객체의 메모리를 관리하는 GC의 부하나, 메모리를 자동으로 처리하는 과정에서 발생하는 여러 문제가 있을 수 있기 때문에, 효율적인 파이썬 코드를 작성하는 것이 중요합니다.

아래는 효율적인 파이썬 코드 작성 예시입니다.

### 1. 문자열에 `+` 연산자 피하기

* 문자열을 연결하기 위해 `+` 연산자를 사용할 경우 문자마다 객체를 생성하기 때문에 새 메모리를 할당합니다.
* 새 메모리할당을 최소화하는 것이 효율적인 파이썬 코드를 작성할 수 있습니다.

```python
# wrong case
msg = "hello" + add_msg + "hello"

# best case
msg = "hello %s world" % add_msg
```

### 2. 제너레이터 사용하기

* 아래 예시를 볼 때, `wrong case`를 보면 새로운 결과를 찾을 때마다 result에 append 매서드를 호출함을 확인할 수 있습니다.
* `best case`의 제너레이터를 사용한 경우 한 번에 모든 항목을 반환하는 것이 하닌 한 번에 하나의 결과를 반환하는 함수를 제공합니다.
* 즉, 리스트의 경우 append 매서드를 사용함으써 메모리를 할당-삭제를 반복하기도하며, 작업 메모리에 모든 입력과 출력을 저장해야함으로 제너레이터를 사용하는 것이 더 효율적인 코드를 작성할 수 있습니다.

```python
# 문장의 공백의 인덱서를 반환하는 코드
# wrong case
def index_words(text):
  result = []
  if text:
    result.append(0)
  for idx, letter in enumerate(text):
    if letter == " ":
      result.append(idx + 1)
  return result

# best case
def index_words_iter(text):
  if text:
    yield 0
  for idx, letter in enumerate(text):
    if letter == " ":
      yield idx + 1
```

(효율적인 파이썬 코드 작성은 추후 추가할 예정입니다.)

Author : robert-min / Last Update : 2023-08-04
