이전 단원에서는 CPU가 하는 일과 그와 함께 등장하는 스레드, 코루틴, 콜백함수와 동기/비동기 등에 대해서 알아봤습니다. 이번 장에서는 CPU가 데이터를 끌어오는 장소 메모리에 대해서 어떻게 활용하는지에 대해서 논의합니다. 특히, 함수의 호출에 대한 스택영역과 동적 할당을 위한 힙 영역에서 활용되는 예시를 아주 상세하게 설명하고 있어서 개인적으로 이해가 잘되었습니다. 마무리로는 Python에서 자동으로 메모리할당/제거 해주는 가비지 컬렉션 외에도 관리할 수 있는 몇가지 방법에 대해서 작성하며 마무리합니다.
1. 메모리의 본질, 포인터와 참조
처음 C언어를 배울 때, 포인터는 많이 좌절하는 구간입니다. C에서는 포인터에 대해서 중요하게 다루는 반면 java와 Python에서는 참조라는 이름으로 다르게 불리웁니다. 그 차이가 무엇일까요?
일단 그 전에 메모리 정의 부터 해야할 것 같습니다. 메모리는 1,0 숫자를 저장하는 컴퓨터의 '사물함'이라고 표현하고 있습니다. 보통 8개의 사물함을 묶어 표현하며 이를 1Byte라고 합니다. 쉽게 말해 부호 없는 정수(unsigned interger)르 2^8까지 즉 0 ~ 255까지 저장할 수 있는 공간입니다. 흔히 Panas로 데이터를 불러 올때, int64 데이터 형식으로 데이터를 불러오게되는데 이는 CPU 처리 형식과 관련이 있는 것임을 알 수 있었습니다.
이런 메모리에 값이 저장이 되고 변수는 그 메모리의 저장 위치를 가리킨다는 직관적인 이해를 할 수 있습니다. 반면 동일한 값을 저장하는 경우에는 굳이 중복되게 메모리에 저장할 필요가 없습니다. 이때 해당하는 데이터의 주소를 저장하므로 포인터의 개념이 등장합니다. 책에서는 포인터는 메모리 주소를 더 높은 수준으로 추상화 한 것이라고 말합니다.
포인터는 C와 같이 직접적으로 주소에 접근 할 수 있어 메모리를 직접 프로그래머가 알 수 있고 수정할 수 있습니다. 하지만 자바나 파이썬은 이를 이런 작업이 불가능합니다. 이런 이유 때문에 C언어가 저수준의 계층을 제어하고 강력한 힘을 가지며 시스템, 임베디드 프로그래밍에 적합한 언어임을 할 수 있습니다. 반면 이런 자율성이 오히려 난이도를 높이는 결과를 가져오기도 합니다.
반면 적은 힘에는 적은 책임이 따르는(?) 포인터가 없는 언어의 경우 "참조(reference)"라고 통칭 됩니다. 참조는 포인터를 한번 더 추상화 한 것이라고 표현합니다.
2. 프로세스의 모습과 스택 영역
왼쪽은 메모리의 프로세스의 모습입니다. 코드 영역과 데이터 영역은 Chapter1에서 설명했던 것 처럼 링커가 만든 실행파일을 초기화 할 때 생성되는 영역입니다. 힙 영역은 동적 메모리 할당에 사용 되는데 C언어에서는 malloc 함수가 요청하고 관리합니다. 주로 프로그래머가 직접 관리하는 영역입니다. 마지막으로 스택 영역은 함수 호출에 사용되며 매개 변수, 반환 주소, 레지스터 정보 등 을 포함한 함수 실행 시 저장하는데 사용하며 필요에 따라 중간의 유휴 영역에 메모리를 사용할 수 있습니다.
스택 영역에서 일어나는 일은 자료 구조의 Stack을 이해했다면 꽤나 쉬워서 간단하게 그림으로 넘어갑니다.
그림엔 표현되어있지 않지만 함수 실행 관점에서는 2가지가 중요한데, 어디서 왔는지에 대한 반환(return)과 어디로 가는지에 대한 정보(jump)의 정보가 필요합니다. 함수A가 다른 함수 B를 호출한다면 이 연계를 통해 CPU에게 끊이지 않고 프로그램 실행을 요청해야하기 떄문입니다. 함수의 스택 프레임은 어디로 가야할지에 대한 정보를 하단에 추가합니다. 반환 주소 외에도 지역변수, 매개 변수를 전달하거나 반환값을 가져오면 역시 스텍 프레임에 추가됩니다. 이로서 기존 유휴 공간이 줄어듭니다.
+ 꼬리 재귀라는 기능이 코틀린에는 있다더라
꼬리 재귀 (Tail Recursion)
재귀 자체도 머리 속에 과정을 떠올리려고 하면 굉장히 추상적이라 어렵게 느껴지는데 꼬리 재귀 개념까지 한번에 이해하려고하니 글을 읽는 것만으로는 잘 이해가 되지 않았다. 개발자 도구를
joooing.tistory.com
3. 힙 영역
지역 변수와 달리 전역변수는 모든 모듈에 노출이 되어있으며 접근 가능합니다. 이를 프로그래머가 직접 제어하는 것이 중요합니다. 왠면 지역 변수는 함수에 종속되어 있어서, 메모리의 사용이 끝나면 메모리를 무효화하기 때문에 해당 정보가 언제까지 유지될지 모르기 때문입니다. 따라서 직접 제어할 수 있는 메모리 영역이 필요해졌고 이를 힙 영역을 활용하기로 하였습니다.
이런 힙 영역은 C언어에서 malloc 함수로 활용하며 여기서 고민할 가지는 4가지로 다음과 같습니다.
- 메모리 유휴 여부를 확인하기
- 메모리 사용 우선 순위
- 할당 후 남은 메모리 처리 방안
- 반환된 메모리의 처리 방안
1. 메모리의 유휴 여부를 확인하는 것은 메모리의 header 영역과 payload 영역을 따로 설정하여 정보를 나타나게 하였습니다. free/allocated 를 뜻하는 f/a 영역에 메모리의 사용 여부를, 조각의크기를 저장하여 메모리의 사용정보를 나타냈습니다.
2. 메모리 사용 우선 순위: 메모리는 파편화되어 있을 수 있기 때문에 어떤 메모리를 먼저 할당하여 효율적으로 사용할지는 상황에 맞는 알고리즘을 써야하며, 최초 적합 / 다음 적하 / 최적 적합 방식을 쓸 수 있습니다. 이 부분에서 쏘카에서 했던 자동차 대여 테트리스 블로그 글이 생각났는데, 메모리는 full scan을 할만큼 고정되어있는 정보이고 자동차 대여는 미래를 알 수 없는 점이라는게 다르네요.
https://tech.socarcorp.kr/data/2022/06/10/reservation-tetris.html
쏘카 예약을 효율적으로 - 수학적 모델링을 활용한 쏘카 예약 테트리스
최적화 문제를 해결하는 과정
tech.socarcorp.kr
딱봐도 너무 알고리즘 문제스러운 것 같아 찾아보니 역시 있더라~
https://leetcode.com/problems/design-memory-allocator/description/
3. 남은 유휴 메모리 처리 방안: 메모리가 해제 될때 즉시 병합하는 것은 어려운 일은 아니나, 해제될 때 마다 병합하는 것은 새로운 메모리 저장 요청이 들어왔을 때 다시 분할의 과정을 거쳐야하는 이슈가 있을 수 있으므로 대부분의 경우에 '연기'를 하는 방식을 선택하고 있다고 합니다. 이것도 사실 상황에 따라 달라질 수 있는 것이라 생각됩니다.
4. 그러면 남은 메모리를 어떻게 효율적으로 병합할 수 있을까요? 그전에 사실 header에는 해당 메모리의 할당 여부와 크기 밖에 없기 때문에 그 전 메모리에 대한 정보를 알 수 없습니다. 따라서 연결 리스트의 개념을 본따 이중 연결 리스트로 해결할 수 있습니다.
이건 여담인데 로컬에서 너무나 큰 데이터를 DBeaver로 불러올 때, 해당 프로그램의 힙 영역 메모리 사이즈를 증가시키라는 해결법이 있다. 그렇다 이게 바로 힙 영역이였다..!!!
https://velog.io/@jyh7ab/DBeaver-Java-heap-space
DBeaver Java heap space
새로운 서버를 만들고 DB를 기존 서에서 가져와야 해서 DBeaver에서 테이블을 export 하였다. 하지만 db용량이 큰 것인가? DBeaver에서 Java heap space 에러가 뜬다 이러면 DBeaver 메모리 용량을 올려주면 된
velog.io
4. 메모리를 할당할 때, 저수준의 계층에서 일어나는 일
x86 CPU는 4가지 단계의 특권 단계(previlege level)을 제공하며, Level 0은 커널 상태 가장 수준 높은 상태. 반면 Level 3는 사용자 상태에서 사용할 수 있는 응용 프로그램이 접근 할 수 있는 단계입니다. 커널모드가 그럼 윈도우에서 관리자 실행 모드 인가 라는 질문에는 No 입니다.
Can normal users program the kernel of an operating system like Windows, Linux, or Mac OS? If yes, what is the process for doing
Answer (1 of 2): The short answer is no. The long answer is of the three OS’s you mention Linux is open source (Mac OS is based on FreeBSD but not open). So technically, if you have the programming skills you could add what ever functionality you want an
www.quora.com
일부 상황에서 응용 프로그램이 운영체재에 서비스를 요청할 수 있는데 이를 시스템 호출(System call)이라고 합니다. 이를 호출하는 단계를 표현하면 다음과 같습니다.
시스템 호출 과정에서 운영체제에 종속되지 않고 범용적으로 사용하기 위한 중간 계층이 있는데 이를 표준 라이브러리라고 합니다.표준 라이브러리는 사용자 상태에서도 실행되며 실행 중인 운영 체제에 따라 대응되는 시스템 호출을 선택합니다. C언어에서 malloc과 같은 메모리 할당자가 바로 이 표준 라이브러리에 존재합니다.
5. 파이썬에서 메모리 관리
c언어에서는 메모리 관리에 대해서 매우 딥하게 배우지만 파이썬은 그렇지 않습니다. del 과 같은 명령어로 객체를 삭제하여 명시적으로 메모리를 확보할 수 있습니다.
- 가비지 컬렉션(Garbage Collection)
- 프로그램이 할당했지만 더 이상 사용하지 않는 메모리를 자동으로 회수해주는 프로그램입니다. 다음 코드로 강제 실행을 할 수 있습니다.
import gc
# 가비지 컬렉션 강제 실행
gc.collect()
- 제너레이터 사용: 함수의 결과를 한번에 반환하지 않고 yield를 통해 하나씩 리턴
# 리스트: 메모리를 한 번에 많이 사용
nums = [i * i for i in range(1000000)]
# 제너레이터: 하나씩 생성 -> 메모리 절약
def generate_nums():
for i in range(1000000):
yield i * i
nums_gen = generate_nums()
# 사용 예시
for num in nums_gen:
if num > 100:
break
- 로컬 함수에 변수 선언
- 함수 내부에서 정의된 변수는 전역 변수보다 접근이 빠르고 함수 실행이 끝나면 자동으로 메모리에서 해제됨!
# 전역 변수 (권장되지 않음)
temp = 0
def compute_sum_global(n):
global temp
for i in range(n):
temp += i
return temp
# 로컬 변수 (권장)
def compute_sum_local(n):
temp = 0 # 로컬 변수
for i in range(n):
temp += i
return temp
print(compute_sum_local(1000000))
- 구현된 함수나 라이브러리 사용
- 표준, 외부 라이브러리는 C로 구현되어있어서 빠르고 메모리 효율적
# 직접 구현한 합계 함수 (비효율)
def custom_sum(lst):
result = 0
for num in lst:
result += num
return result
# 내장 함수 sum 사용 (권장)
nums = list(range(1000000))
print(sum(nums)) # 훨씬 빠르고 메모리 효율적
- 반복문에 itertools 사용
import itertools
# 일반 for문으로 두 리스트의 모든 조합 생성 (메모리 낭비 가능)
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
product_manual = [(x, y) for x in list1 for y in list2]
# itertools.product 사용 (lazy evaluation)
product_lazy = itertools.product(list1, list2)
for pair in product_lazy:
print(pair)
- 메모리 타입 최적화
import pandas as pd
import numpy as np
# 비효율적인 타입
df = pd.DataFrame({
'id': range(10000),
'category': ['A'] * 5000 + ['B'] * 5000
})
# 메모리 최적화
df['id'] = df['id'].astype(np.int16)
df['category'] = df['category'].astype('category')
print(df.info(memory_usage='deep'))
6. 출처
https://pbpython.com/pandas_dtypes.html
Overview of Pandas Data Types - Practical Business Python
Mon 26 March 2018 Posted by Chris Moffitt in articles Introduction When doing data analysis, it is important to make sure you are using the correct data types; otherwise you may get unexpected results or errors. In the case of pandas, it will correctl
pbpython.com
https://schoolcoders.com/programming-techniques/variables-and-expressions/variables-and-assignment/
https://en.wikipedia.org/wiki/Pointer_%28computer_programming%29
Pointer (computer programming) - Wikipedia
From Wikipedia, the free encyclopedia Object which stores memory addresses in a computer program I do consider assignment statements and pointer variables to be among computer science's "most valuable treasures." A pointer a pointing to the memory address
en.wikipedia.org
https://dlfdn91.medium.com/17-%EC%9E%AC%EA%B7%80%ED%95%A8%EC%88%98-recursion-c6a5ab37309c
17. 재귀함수 ( Recursion )
☞ 재귀함수란?
dlfdn91.medium.com
https://www.geeksforgeeks.org/doubly-linked-list/
Doubly Linked List Tutorial - GeeksforGeeks
Your All-in-One Learning Portal: GeeksforGeeks is a comprehensive educational platform that empowers learners across domains-spanning computer science and programming, school education, upskilling, commerce, software tools, competitive exams, and more.
www.geeksforgeeks.org
https://yomangstartup.tistory.com/105
파이썬 - 메모리 관리와 좋은 습관
1. 시작 파이썬은 메모리 관리를 자동으로 해주는 언어입니다. 그러면 우리는 왜??? 파이썬 메모리 관리에 대해서 배워야 하는 것일까요? 파이썬에서는 효율적인 메모리 관리를 위해 수만 줄의
yomangstartup.tistory.com
'Data Science > 컴퓨터 밑바닥의 비밀' 카테고리의 다른 글
컴퓨터 밑바닥 Chapter 5: 작은 것으로 큰 성과 이루기, 캐시 (1) | 2025.07.06 |
---|---|
컴퓨터밑바닥 Chapter 2: 프로그램이 실행되었지만, 뭐가 뭔지 하나도 모르겠다. (0) | 2025.06.01 |
컴퓨터밑바닥: Chapter 1 프로그램 언어부터 실행까지, 이렇게 진행된다. (0) | 2025.05.18 |