2025. 6. 9. 13:11ㆍ연구하기, 지식
서론
파이썬 코드의 가장 큰 단점 중 하나는 실행 속도라고 할 수 있다. C언어보다 쉽다는 장점이 있으나 아직까지 C언어의 속도는 따라갈 수 는 없다. 그래서 빠른 실행 속도를 요구하는 서비스에서는 아직도 C의 선호도가 압도적이다. 이런 C의 속도를 따라가기 위한 뱁새 가랑이인 JIT 컴파일러에 대해 알아 보겠다. 그리고 코드의 가독성을 높이는 데코레이터까지 추가적으로 알아보겠다. 코딩, 특히 실무에서는 속도와 가독성이 생명이니깐.
왜 느린가?
C는 컴파일 방식이고 파이썬은 인터프리터 방식이다. 그래서 느리다. 쉽게 말하자면 밀키트로 한 방에 요리하기 vs 재료 하나하나 다 준비해가며 요리하기의 차이라고 볼 수있다.
- 컴파일 방식 : 코드를 한번에 기계어로 번역해서 실행.
- 인터프리터 방식 : 코드를 한 줄 한 줄 읽으며 그때 그때 해석하며 실행.
JIT(Just In Time) 컴파일
Numba: A High Performance Python Compiler
Numba makes Python code fast Numba is an open source JIT compiler that translates a subset of Python and NumPy code into fast machine code. Learn More Try Numba »
numba.pydata.org
파이썬에는 Numba라는 라이브러리가 있다. 함수 실행 속도를 C나 FORTRAN 수준으로 내기 위해 기계어로 번역하는 라이브러리이다. 인터프리터 교체나 별도의 컴파일 혹은 컴파일러 설치도 필요 없다. 데코레이터를 함수에 붙이기만 하면 된다. 다만 첫 실행 때 번역 작업이 들어가 약간의 시간이 소요되지만 이 번역된 함수는 캐쉬에 저장되어 반복 호출 시 상당한 이점을 같는다. 반복문과 NumPy 배열 계산에는 상당한 효과가 있지만 딕셔너리, 문자열, 클래스, 파이썬 내장 함수, 그리고 너무 복잡한 동적 구조는 오히려 느릴 수도 있다. 예시 코드는 다음과 같다.
import numpy as np
import time
from numba import njit
# Numba를 적용한 함수
@njit
def fast_sum(x):
total = 0.0
for i in range(len(x)):
total += x[i]
return total
# Numba 없이 순수 Python으로 작성한 동일한 함수
def slow_sum(x):
total = 0.0
for i in range(len(x)):
total += x[i]
return total
# 테스트용 데이터
arr = np.random.rand(1_000_000)
# 순수 Python 함수 실행 시간 측정
start = time.time()
slow_result = slow_sum(arr)
end = time.time()
print(f"slow_sum result: {slow_result:.6f}, time: {end - start:.6f} seconds")
# Numba JIT 컴파일을 위한 첫 실행 (컴파일 포함)
start = time.time()
fast_result_first = fast_sum(arr) # 워밍업 (컴파일 발생)
end = time.time()
print(f"fast_result_first: {fast_result_first:.6f}, time: {end - start:.6f} seconds")
# Numba 최적화 함수 실행 시간 측정
start = time.time()
fast_result = fast_sum(arr)
end = time.time()
print(f"fast_sum result: {fast_result:.6f}, time: {end - start:.6f} seconds")
## 실행 결과
slow_sum result: 500076.177902, time: 0.366021 seconds
fast_result_first: 500076.177902, time: 0.126290 seconds
fast_sum result: 500076.177902, time: 0.001915 seconds
0 ~ 1 사이의 1,000,000 개의 난수를 생성해 넘파이 배열 연산하는 작업에서 첫 실행 시는 약 35% 속도, 두번째는 약 0.5%의 속도라는 말도 안되는 차이를 지닌다. 여기서 사용한 거라곤 그저 @njit 라는 데코레이터를 함수에 붙인 것 밖에 없다. 그럼 데코레이터란 뭘까?
* 파이썬 3.6 이상 부터는 가독성의 위해 숫자에 _를 넣는 것이 가능하다. 쉼표처럼 말이다. 다음과 같이 가독성이 상당히 높아진다.
데코레이터
파이썬에서 데코레이터는 기존 함수에 새로운 기능을 추가하는 래퍼(wrapper) 역할을 한다. 이리저리 여러 함수에 공통으로 사용되는 로깅, 실행 시간 측정 등의 기능을 데코레이터에 선언해주고 그저 적용할 함수 위에 @decorator 이런 식으로 붙여주면 된다.
def decorator(func):
def wrapper(*args, **kwargs):
# 함수 호출 전 처리
print("함수 호출 전 실행")
result = func(*args, **kwargs) # 원본 함수 호출
# 함수 호출 후 처리
print("함수 호출 후 실행")
return result
return wrapper
@decorator
def say_hello(name):
print(f"Hello, {name}!")
say_hello("미어캣")
## 실행 결과
함수 호출 전 실행
Hello, 미어캣!
함수 호출 후 실행
이렇듯 함수 전체를 감싸 코드 실행 위치까지 설정한 후에 활용할 수 있어 매우 경제적이고 유지 보수도 용이하다. 그리고 보기에도 좋아 이쁘다. 굳이 치덕치덕 함수를 호출하지 않아도 된다. 익숙 하지 않은 것은 원본 함수 호출 부 일 것이다. *args와 *kwargs는 무엇일까?
- *args : 위치인자로 여러 개의 위치 인자를 튜플 형태로 받는 다는 뜻이다. 즉, test(1, 2, 3) 이렇게 받는 다는 뜻.
- **kwargs : 키워드 인자로 여러 개의 인자를 딕셔너리 형태로 받는 다는 뜻이다. 즉, test(a = 1, b = 2) 이런 식으로 받는 다는 뜻.
즉, 원본 함수가 받는 모든 인자들을 그대로 처리하겠다는 뜻이다. 범용적인 데코레이터를 만들 때 많은 호출 시나리오를 커버하기 위해 저렇게 선언 해준다. 뿐만 아니라 많은 오픈 소스 클래스들이 저런 식으로 되어 있다. 왠만하면 두 개 다 써주자.
데코레이터 활용
데코레이터도 활용이 가능하다. 실행 속도를 올릴 수 도 있다. 예를 들어, 피보나치 수열을 계산하는 코드가 다음과 같이 있다.
def memorize(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@memorize
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(35))
fibonacci는 중복 계산이 매우 많은 재귀 함수이다. 중복 계산 방지를 위해 memorize라는 데코레이터를 생성하고 fibonacci(n)에 대한 값을 캐싱 해둔다. 해당 호출이 일어나면 다시 계산하지 않고 cache 딕셔너리에서 꺼내쓰는 데코레이터를 선언해주기만 해면 2초대 실행 시간에서 0초대로 줄어 든다. 또한 매개변수를 사용할 수 도 있다.
def repeat(times): # 데코레이터 생성기 역할
def decorator(func): # 실제 데코레이터
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(times=3)
def greet():
print("안녕하세요!")
greet()
## 실행 결과
안녕하세요!
안녕하세요!
안녕하세요!
이렇게 데코레이터 선언 전에 함수처럼 선언하고 매개변수를 지정해주면 데코레이터 안에서 해당 매개변수를 활용할 수 있다. 이렇듯 데코레이터를 적절히 잘 활용해준다면 실행 속도 개선과 예쁜 코드를 짤 수 있다. 유지보수는 덤이다.
'연구하기, 지식' 카테고리의 다른 글
Supabase DB 사용해보기 (3) | 2025.07.06 |
---|---|
Gemma3 - 무료 멀티모달 LLM 모델 사용하기 (3) | 2025.06.29 |
푸리에 변환(Fourier transform, FT) (2) | 2025.06.02 |
NeMo Speaker Recognition(SR) 성능 올리기 (6) | 2025.05.25 |
파이썬에서 오디오 파일 다루기(pydub, soundfile) (0) | 2025.05.25 |