파이썬이 컴파일을 한다고? - Python Compile

최근 Python 딥다이브를 하면서 Python 동작 원리부터, cpython을 들여다보려는 노력을 하고 있다.
너무 깊게 다이빙하다가 자꾸 올라오긴 하는데,,,

 

아무튼, 파이썬 언어의 동작 원리에 대한 이해 없이 코딩 테스트와 구현만 하다보니깐, 내가 파이썬을 제대로 쓰고 있는건가? 하는 의문이 들었다.

 

사실 Java나 Go를 배워보고 싶지만 시간도 없고,  Python이라도 제대로 알고 넘어가자라는 생각으로 기초부터 단단하게 쌓으려는 노력 중이다.

 

오늘은 내가 작성한 Python Script가 어떤 과정을 거쳐서 실행되는지에 대해서 이해할 수 있었다.

그 과정에서 Python도 컴파일을 한다라는 사실을 알게 되었고, 다른 언어와의 차이를 확인해봤다.


# Python Interpreter

파이썬은 인터프리터 언어이다.

이는 "Python 소스 코드를 Interpreter라는 별도의 프로그램 통해 별도의 실행 파일 없이 실행된다." 라고 표현할 수 있다.

 

python은 C언어로 구현되었다는 얘기를 여러번 들어봤는데, Python 공식 문서를 통해 다운로드 받게되면 cPython 구현체를 사용하게 된다. cpython 말고 다른 구현체도 존재한다. jython, pypy 등

 

아무튼, Python Interpreter를 통해 Python Script로 작성한 코드 최종적으로 기계어로 변환되지만, 바로 기계어로 떨어지는 것이 아니라 바이트 코드를 거친다.

 

즉, Python Script(.py) -> Byte Code(.cpy) -> Machine Language 의 과정을 거친다.

이를 표현한 다이어그

 

엄격하게 구분하면, Python 소스코드는 인터프리터를 통해 Byte Code로 컴파일되고, Python VM에서 해당 코드를 실행시키는 구조를 갖는다.

 

JVM처럼 PVM이 구분지어지진 않고, 일반적으로 Python Interpreter로 통칭된다.

 

Byte Code를 사용하는 이유는 실행 환경과 독립적이고, Python 스크립트보다 빠르기 때문에 중간 단계로 컴파일되어 사용한다.

 

PVM에서는 인터프리터가 컴파일한 바이트 코드를 실행하고, 메모리 관리, 예외 처리, 스레딩, 파일 입출력 등의 자원 관리를 담당한다.

 

이 PVM은 사용되는 운영체제에 따라 다르다.


C 언어(컴파일 언어)와의 차이

그러면 왜 Python은 컴파일 과정을 거치는데, 컴파일 언어인 C와 다를까?

 

C 언어에 대해서 딥다이브를 하진 못했고, 간략한 비교로 이를 정리해봤다.

 

C 언어로 작성한 코드는, 명시적인 빌드(컴파일)를 수행하고 (Linking을 거쳐) 생성된 실행 파일을 통해서 코드의 내용이 실행된다.

 

즉, C 언어는 소스 코드를 직접 기계어로 변환하여 실행 파일을 생성하는데, 이 실행 파일은 운영체제에서 실행된다, 따라서 해당 실행 파일은 플랫폼(Mac, Windows, Linux)에 종속되어서 다른 플랫폼에서 사용할 수 없다.

 

Python은 소스 코드를 바이트 코드로 컴파일한 후 Python VM에서 시스템에 맞게 바이트 코드를 해석해서 실행된다. 이 과정에서 Python은 별도의 실행 파일을 생성하지 않고, 소스 코드를 한줄씩 읽어들여 실행한다.

 

두 언어의 동작 방식의 차이는 컴파일 자체의 유무가 아니라, 해당 코드를 어떻게 컴파일하고 실행하느냐에 있는 것으로 이해했다.

 

이를 다시한번 정리하면,

 

전통적인 컴파일 언어인 C는 전체 소스 코드를 한번에 컴파일 후 링킹을 거쳐서 하나의 실행 가능한 파일로 변환된다. 해당 파일은 기계어로 구성되어 있으므로 실행 속도가 빠르다
또한, 컴파일 과정과 실행 과정이 분리되어 있으므로, 동일한 소스 코드에 대해서 컴파일은 한번만 수행하면 된다.

 

인터프리터 언이인 Python은 전체 소스 코드를 한줄씩 읽어 들여서 Byte Code로 변환한 후 이를 PVM에서 실행한다. 따라서, 별도의 실행 파일이 존재하지 않고 중간 단계를 거치기 때문에 실행 속도가 느리다.
소스코드의 컴파일과 실행이 함께 이루어지므로, 코드를 실행할 때마다 컴파일 과정이 소요된다(cpython은 이를 위해 캐싱을 적용).


# Byte code 확인

Python 코드에서 변환되는 바이트 코드를 실제로 확인할 수 있는데, 이는 dis 모듈을 사용하면 된다.

임의로 작성한 함수의 바이트 코드

 

작성한 Python Script가 Byte Code로 변환되는 과정을 이해하기 위해 실행해보았다.


# cpython의 캐싱

Python을 사용하다보면, 항상 실행 경로에 __pycache__ 디렉토리가 존재함을 알 수 있었다.

 

기존에는 이 디렉토리의 쓰임새에 대해서 생각해보지 않았는데, Python의 동작 과정을 보면 이를 쉽게 이해할 수 있다.

 

인터프리터 언어인 Python은 컴파일과 실행이 함께 수행되고, 이런 동작 방식은 실행 시간의 증가를 가져온다.

 

이를 해결하기 위한 방법 중 하나로 cpython은 바이트 코드를 캐싱해둠으로써 성능을 향상시켰다. 


소스 코드를 컴파일한 바이트 코드를 따로 저장해두고, 이 바이트 코드가 저장되는 디렉토리가 __pycache__ 이다.

위에서 실행한 test2.py의 캐싱 파일 .pyc

 

해당 디렉토리에 존재하는 .pyc 파일을 통해서 바이트 코드를 확인해볼 수 있다. UTF-8 인코딩이 깨져서 무슨 의민지는 모르겠다.

 

만약, 별도로 성능을 위해 실행할 스크립트의 바이트 코드를 미리 컴파일해놓고 싶다면, 

 

python -m compileall .

 

이 명령어를 통해 바이트 코드로 컴파일할 수 있다.


# 정리

Python의 실행 과정을 이해하고, __pycache__의 의미에 대해 이해할 수 있었다.

 

차차 기본 함수와 객체 타입에 대해서 cpython을 직접 들여다보는 시간을 가져야겠다. 

 

"Python도 Java의 JAR처럼 기본 패키징 방식이 있었으면 조금 더 웹 개발에 선호되었을까?"하는 생각을 해보면서, "다음에는 별도 패키징 라이브러리를 사용해보자" 라는 생각으로 글을 마무리한다.

728x90
반응형