[Python] 모듈과 메모리 사용량

Python은 태생적인 한계로 인해 메모리 사용에 있어서 매우 비효율적인 부분이 많이 존재한다.

특히 모듈을 사용하는 부분에 있어서 비합리적인 부분이 많다. 모듈을 추가할때마다 그만큼 메모리 사용량이 선형적으로 증가하기 때문이다.

이 문제는 다음 특성들로 인해 돌출된다.


첫번째 - 컴파일 없음
Python은 컴파일 과정이 존재하지 않는 스크립트형 언어라서 모든 라이브러리는 사용하든 않든 전부 메모리에 올린 채로 실행한다.
그래서 전체 소스코드 크기가 100mb인 모듈을 import하면, 즉시 기본 메모리 사용량이 100mb 증가한다.


두번째 - Tree Shaking 개념의 부재 (Dead Code Elimination)
컴파일을 하지 않더라도, 라이브러리 코드 중에서도 사용하는 것만 유지하고 버리는 매커니즘을 구현하는 것은 논리적으로 가능하다.
Node.js 환경만 해도 Tree Shaking이라고 해서 컴파일까진 아니더라도 미사용 코드를 덜어내서 라이브러리 사용으로 인한 부담을 최대한 줄이는 패턴을 주로 사용한다. 문법적인 장치도 약간 두긴 하지만.
https://blog.naver.com/sssang97/223873640521
하지만 Python은 브라우저처럼 빠른 로딩을 중요시하는 부분에서 전혀 사용되지 않아서인지, 이런 방면에서는 환경이 전혀 갖춰져있지 않다. 지나친 문법 유연성으로 import 구문을 런타임에 아무때나 날릴 수 있다는 것도 이러한 제한에 한몫 했다.


세번째 - 멀티프로세스 패턴
Python은 스레드를 제대로 지원하지 않았기 때문에 멀티프로세스 기반으로 동시성을 흉내내는 경우가 매우 많다.
FastAPI 같은 경우만 하더라도 멀티코어를 활용하고 동시 처리 능력을 향상시키기 위해서 멀티프로세스(멀티 워커) 구성으로 사용하는 것이 일반적인 사용 패턴이다.
근데 당연히 각 프로세스는 독립적이니, 100mb 짜리 모듈을 하나 추가했더라도 워커가 10개라면 메모리 사용량은 100gb가 아닌 1gb가 되어버린다.


사실 Python은 이런 방면에서는 매우 미숙한 언어라서 이 문제를 멋지게 해결할 방법은 딱히 없다.
Python을 선택한 죄다.

그래도 다음과 같은 방안들을 고려해볼 수는 있을 것이다.




모범 답안: 모듈 줄이기

당연하지만, 모듈 사용으로 인한 메모리 압력을 줄이는 방법은 모듈을 줄이는 것이다.

미사용 모듈을 제거하고, 기능이 중복되는 모듈이 있다면 통합하는 작업 등을 하는 것을 권장한다.

그리고, 모듈이 설치가 되어있더라도 import를 하지만 않으면 메모리에 모듈을 로드하지 않는다.

사용하지 않거나 의미없는 import가 있다면 최대한 줄여보자.




네이티브 컴파일

일반적인 방법은 아니지만, 그냥 python을 컴파일해서 실행하는 방법도 있다.
기본적인 연산 성능도 훨씬 나아지겠지만 실행파일이 매우 작아진다는 것이 가장 큰 장점이다.
모듈을 추가로 로드할 필요도 없다.

물론 CPython과의 호환성 수준이 완벽한 툴체인은 하나도 없기 때문에, 상황에 따라서는 컴파일이 아예 불가능할 확률이 꽤 높다. Python 에코시스템들이 어지간히 개판이어야지.

대표적인 컴파일 선택지로는 GraalPy, Pypy 등이 있다.
이 중 Pypi는 버전 지원이 3.11에서 끊겼고, 호환성이 완벽하지는 않다.
GraalVM은 버전 지원이 3.8까지만 된다.

이외에도 마이너한 툴체인들이 몇개 있긴 한데, 신뢰성은 잘 모르겠다.


이게 다 안되면, 그냥 메모리를 늘리는 것 밖에 없다. 포기하자.