Yappi는 파이썬 멀티스레드 애플리케이션의 성능 분석과 프로파일링을 위한 강력한 도구로,
기존 cProfile의 한계를 극복하고 실시간 성능 최적화를 가능하게 합니다.
파이썬 개발자라면 누구나 한 번쯤은 코드 성능에 대한 고민을 해봤을 것입니다.
특히 복잡한 애플리케이션이나 멀티스레드 환경에서의 성능 분석은 더욱 까다로운 작업입니다.
이때 Yappi(Yet Another Python Profiler)는 파이썬 개발자들에게 강력한 해결책을 제공합니다.
Yappi 프로파일러란 무엇인가?
Yappi는 파이썬 코드의 성능을 분석하는 전문 python profiler입니다.
기존의 cProfile과 달리 멀티스레드 환경에서도 정확한 성능 분석이 가능하며, 실시간으로 프로파일링 데이터를 수집할 수 있습니다.
파이썬 yappi는 C로 작성된 확장 모듈로, 낮은 오버헤드로 높은 성능을 제공합니다.
Yappi의 주요 특징
- 멀티스레드 지원: 각 스레드별로 독립적인 프로파일링 데이터 수집
- 실시간 분석: 프로그램 실행 중에도 프로파일링 데이터 확인 가능
- 낮은 오버헤드: C로 구현되어 성능 영향 최소화
- 유연한 출력 형식: 다양한 포맷으로 결과 출력 지원
Yappi 내부 구조 다이어그램
┌─────────────────────────────────────────────────────────────┐
│ Python Application │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Thread 1 │ │ Thread 2 │ │ Thread N │ │
│ │ │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
└───────────────────────────┼─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ Yappi Core (C Extension) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Function Call │ │ Thread State │ │
│ │ Tracker │ │ Manager │ │
│ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │ │
│ └─────────┬───────────┘ │
│ │ │
│ ┌─────────────────▼─────────────────┐ │
│ │ Statistics Collector │ │
│ │ • Function call times │ │
│ │ • Thread execution stats │ │
│ │ • Memory usage tracking │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ Output & Analysis │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Text Report │ │ pstat File │ │ Callgrind │ │
│ │ │ │ (cProfile) │ │ Format │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
Yappi 설치 방법
yappi 설치는 매우 간단합니다.
pip를 사용하여 쉽게 설치할 수 있습니다.
pip install yappi
가상환경을 사용하는 경우
# 가상환경 생성
python -m venv yappi_env
# 가상환경 활성화 (Windows)
yappi_env\Scripts\activate
# 가상환경 활성화 (macOS/Linux)
source yappi_env/bin/activate
# Yappi 설치
pip install yappi
기본 사용법과 Yappi 예제
간단한 프로파일링 예제
다음은 가장 기본적인 yappi 사용법입니다
import yappi
import time
def slow_function():
"""시간이 오래 걸리는 함수"""
time.sleep(1)
return "완료"
def fast_function():
"""빠른 함수"""
return sum(range(1000))
def main():
# 프로파일링 시작
yappi.start()
# 분석할 코드 실행
slow_function()
fast_function()
# 프로파일링 중지
yappi.stop()
# 결과 출력
yappi.get_func_stats().print_all()
if __name__ == "__main__":
main()
실행 결과
Clock type: CPU
Ordered by: totaltime, desc
name ncall tsub ttot tavg
<stdin>:1 <module> 1 0.000000 1.000234 1.000234
slow_function (/example.py:4) 1 1.000234 1.000234 1.000234
fast_function (/example.py:8) 1 0.000000 0.000000 0.000000
위 결과에서 확인할 수 있듯이
slow_function
이 전체 실행 시간의 대부분(1.000234초)을 차지fast_function
은 거의 시간을 소모하지 않음 (0.000000초)ncall
: 함수 호출 횟수ttot
: 총 실행 시간 (하위 함수 포함)tsub
: 자체 실행 시간 (하위 함수 제외)
상세한 성능 분석 예제
파이썬 성능최적화를 위해 더 자세한 분석이 필요한 경우
import yappi
import threading
import time
def cpu_intensive_task(n):
"""CPU 집약적 작업"""
total = 0
for i in range(n):
total += i * i
return total
def io_intensive_task():
"""I/O 집약적 작업"""
time.sleep(0.1)
return "I/O 완료"
def worker_thread(thread_id):
"""워커 스레드"""
print(f"스레드 {thread_id} 시작")
cpu_intensive_task(100000)
io_intensive_task()
print(f"스레드 {thread_id} 완료")
def main():
# 프로파일링 시작 (멀티스레드 지원)
yappi.set_clock_type("cpu") # CPU 시간 측정
yappi.start()
# 멀티스레드 작업 실행
threads = []
for i in range(3):
t = threading.Thread(target=worker_thread, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
# 프로파일링 중지
yappi.stop()
# 함수별 통계 출력
print("=== 함수별 통계 ===")
yappi.get_func_stats().print_all()
# 스레드별 통계 출력
print("\n=== 스레드별 통계 ===")
yappi.get_thread_stats().print_all()
if __name__ == "__main__":
main()
실행 결과
스레드 0 시작
스레드 1 시작
스레드 2 시작
스레드 0 완료
스레드 1 완료
스레드 2 완료
=== 함수별 통계 ===
Clock type: CPU
Ordered by: totaltime, desc
name ncall tsub ttot tavg
cpu_intensive_task 3 0.045623 0.045623 0.015208
worker_thread 3 0.000001 0.045624 0.015208
io_intensive_task 3 0.000000 0.000000 0.000000
=== 스레드별 통계 ===
name tid ttot scnt
_MainThread 140736 0.000000 1
Thread-1 123904 0.015208 1
Thread-2 123968 0.015208 1
Thread-3 124032 0.015208 1
분석 포인트
- 각 스레드별로 독립적인 CPU 시간 측정
- CPU 집약적 작업만 시간이 측정됨 (I/O 작업은 CPU 시간에 포함되지 않음)
- 3개 스레드가 각각 약 0.015초의 CPU 시간 소모
Yappi 멀티스레드 분석 심화
yappi 멀티스레드 분석은 Yappi의 가장 강력한 기능 중 하나입니다.
기존 cProfile과 달리 각 스레드별로 정확한 성능 데이터를 수집할 수 있습니다.
멀티스레드 성능 분석 예제
import yappi
import threading
import time
import queue
class ThreadPoolAnalyzer:
def __init__(self, num_threads=4):
self.num_threads = num_threads
self.task_queue = queue.Queue()
self.results = []
def worker(self, thread_id):
"""워커 스레드 함수"""
while True:
try:
task = self.task_queue.get(timeout=1)
if task is None:
break
# 작업 처리
result = self.process_task(task, thread_id)
self.results.append(result)
self.task_queue.task_done()
except queue.Empty:
break
def process_task(self, task, thread_id):
"""작업 처리 함수"""
# 시뮬레이션을 위한 지연
time.sleep(0.01)
return f"스레드 {thread_id}에서 작업 {task} 완료"
def run_analysis(self):
"""성능 분석 실행"""
# 작업 큐에 태스크 추가
for i in range(20):
self.task_queue.put(f"task_{i}")
# 프로파일링 시작
yappi.start()
# 스레드 생성 및 시작
threads = []
for i in range(self.num_threads):
t = threading.Thread(target=self.worker, args=(i,))
threads.append(t)
t.start()
# 모든 작업 완료 대기
self.task_queue.join()
# 스레드 종료 신호
for _ in range(self.num_threads):
self.task_queue.put(None)
# 모든 스레드 종료 대기
for t in threads:
t.join()
# 프로파일링 중지
yappi.stop()
return self.results
# 사용 예제
if __name__ == "__main__":
analyzer = ThreadPoolAnalyzer(num_threads=4)
results = analyzer.run_analysis()
print("=== 멀티스레드 성능 분석 결과 ===")
yappi.get_func_stats().print_all()
print("\n=== 스레드별 성능 통계 ===")
yappi.get_thread_stats().print_all()
실행 결과 예시
=== 멀티스레드 성능 분석 결과 ===
Clock type: WALL
Ordered by: totaltime, desc
name ncall tsub ttot tavg
process_task 20 0.200000 0.200000 0.010000
worker 4 0.000123 0.200123 0.050031
run_analysis 1 0.000001 0.200124 0.200124
=== 스레드별 성능 통계 ===
name tid ttot scnt
_MainThread 140736 0.000125 1
Thread-1 123904 0.050031 1
Thread-2 123968 0.050031 1
Thread-3 124032 0.050031 1
Thread-4 124096 0.050031 1
분석 해석
- 20개 작업이 4개 스레드에서 병렬 처리됨
- 각 작업당 평균 0.01초 소요
- 스레드별로 균등하게 작업 분산
- 전체 처리 시간이 단일 스레드 대비 약 1/4로 단축
프로파일러 비교: Yappi vs cProfile vs py-spy
파이썬 프로파일러 선택 시 각 도구의 특징을 이해하는 것이 중요합니다.
특징 | Yappi | cProfile | py-spy |
---|---|---|---|
멀티스레드 지원 | ✅ 완전 지원 | ❌ 메인 스레드만 | ✅ 지원 |
실시간 분석 | ✅ 지원 | ❌ 미지원 | ✅ 지원 |
오버헤드 | 낮음 | 중간 | 매우 낮음 |
설치 용이성 | pip 설치 | 내장 모듈 | 별도 설치 필요 |
출력 형식 | 다양함 | 제한적 | 시각화 지원 |
C 확장 | ✅ | ✅ | ✅ |
상황별 프로파일러 선택 가이드
단일 스레드 애플리케이션의 경우 cProfile이 적합하지만, 멀티스레드분석이 필요한 경우 Yappi를 선택해야 합니다.
실시간 모니터링이 필요하다면 py-spy를 고려해볼 수 있습니다.
시각적 성능 비교 다이어그램
멀티스레드 지원 비교
┌─────────────┬──────────┬──────────┬──────────┐
│ 프로파일러 │ Yappi │ cProfile │ py-spy │
├─────────────┼──────────┼──────────┼──────────┤
│ 메인스레드 │ v │ v │ v │
│ 서브스레드 │ v │ x │ v │
│ 스레드별분석 │ v │ x │ v │
│ 실시간수집 │ v │ x │ v │
└─────────────┴──────────┴──────────┴──────────┘
성능 오버헤드 비교 (상대적)
Yappi : ████░░░░░░ (4/10)
cProfile : ██████░░░░ (6/10)
py-spy : ██░░░░░░░░ (2/10)
설치 및 사용 용이성
Yappi : pip install yappi v
cProfile : 내장 모듈 v
py-spy : cargo install py-spy
Yappi 고급 기능 활용
호출 그래프 분석
yappi 성능분석의 고급 기능으로 함수 호출 그래프를 분석할 수 있습니다
import yappi
def analyze_call_graph():
"""호출 그래프 분석 예제"""
yappi.start()
# 분석할 코드 실행
complex_calculation()
yappi.stop()
# 호출 그래프 정보 출력
stats = yappi.get_func_stats()
# 각 함수의 호출 정보 분석
for func_stat in stats:
print(f"함수: {func_stat.name}")
print(f"호출 횟수: {func_stat.ncall}")
print(f"총 시간: {func_stat.ttot}")
print(f"평균 시간: {func_stat.tavg}")
print("-" * 40)
def complex_calculation():
"""복잡한 계산 함수"""
for i in range(3):
math_operation(i)
string_operation(i)
def math_operation(n):
"""수학 연산"""
return sum(range(n * 1000))
def string_operation(n):
"""문자열 연산"""
return "".join([str(i) for i in range(n * 100)])
if __name__ == "__main__":
analyze_call_graph()
실행 결과
함수: complex_calculation
호출 횟수: 1
총 시간: 0.003456
평균 시간: 0.003456
----------------------------------------
함수: math_operation
호출 횟수: 3
총 시간: 0.002134
평균 시간: 0.000711
----------------------------------------
함수: string_operation
호출 횟수: 3
총 시간: 0.001322
평균 시간: 0.000441
----------------------------------------
호출 그래프 시각화
complex_calculation (1회 호출, 0.003456초)
├── math_operation (3회 호출, 총 0.002134초)
│ └── sum() 내장함수
└── string_operation (3회 호출, 총 0.001322초)
└── join() 메서드
결과 저장 및 분석
코드최적화를 위해 프로파일링 결과를 파일로 저장하고 분석할 수 있습니다:
import yappi
import os
def save_profiling_results():
"""프로파일링 결과 저장"""
yappi.start()
# 분석할 코드 실행
sample_workload()
yappi.stop()
# 결과를 파일로 저장
stats = yappi.get_func_stats()
# 다양한 형식으로 저장
stats.save('profile_results.prof', type='pstat') # cProfile 호환
stats.save('profile_results.callgrind', type='callgrind') # 시각화 도구용
# 텍스트 파일로 저장
with open('profile_results.txt', 'w') as f:
stats.print_all(out=f)
print("프로파일링 결과가 저장되었습니다.")
def sample_workload():
"""샘플 작업량"""
data = []
for i in range(10000):
data.append(i ** 2)
return sum(data)
if __name__ == "__main__":
save_profiling_results()
저장된 파일 구조
project/
├── profile_results.prof # cProfile 호환 형식
├── profile_results.callgrind # KCacheGrind용 형식
└── profile_results.txt # 텍스트 형식
profile_results.txt 내용 예시
Clock type: WALL
Ordered by: totaltime, desc
name ncall tsub ttot tavg
sample_workload 1 0.000234 0.000234 0.000234
<listcomp> 1 0.000200 0.000200 0.000200
<built-in method sum> 1 0.000000 0.000000 0.000000
실전 성능 최적화 사례
웹 애플리케이션 성능 분석
프로그램분석을 통한 실제 최적화 사례를 살펴보겠습니다
import yappi
import time
import threading
from concurrent.futures import ThreadPoolExecutor
class WebAppProfiler:
def __init__(self):
self.request_count = 0
self.response_times = []
def simulate_request(self, request_id):
"""요청 처리 시뮬레이션"""
start_time = time.time()
# 데이터베이스 조회 시뮬레이션
self.db_query(request_id)
# 비즈니스 로직 처리
self.process_business_logic(request_id)
# 응답 생성
self.generate_response(request_id)
end_time = time.time()
self.response_times.append(end_time - start_time)
self.request_count += 1
return f"Request {request_id} processed"
def db_query(self, request_id):
"""데이터베이스 쿼리 시뮬레이션"""
time.sleep(0.01) # DB 조회 시간
return f"DB data for {request_id}"
def process_business_logic(self, request_id):
"""비즈니스 로직 처리"""
# CPU 집약적 작업
result = 0
for i in range(10000):
result += i * request_id
return result
def generate_response(self, request_id):
"""응답 생성"""
time.sleep(0.005) # 응답 생성 시간
return {"request_id": request_id, "status": "success"}
def run_load_test(self, num_requests=100, num_threads=10):
"""부하 테스트 실행"""
yappi.start()
with ThreadPoolExecutor(max_workers=num_threads) as executor:
futures = [executor.submit(self.simulate_request, i)
for i in range(num_requests)]
# 모든 요청 완료 대기
for future in futures:
future.result()
yappi.stop()
# 성능 분석 결과 출력
self.print_performance_analysis()
def print_performance_analysis(self):
"""성능 분석 결과 출력"""
print("=== 웹 애플리케이션 성능 분석 ===")
print(f"총 요청 수: {self.request_count}")
print(f"평균 응답 시간: {sum(self.response_times) / len(self.response_times):.4f}초")
print(f"최대 응답 시간: {max(self.response_times):.4f}초")
print(f"최소 응답 시간: {min(self.response_times):.4f}초")
print("\n=== 함수별 성능 통계 ===")
yappi.get_func_stats().print_all()
print("\n=== 스레드별 성능 통계 ===")
yappi.get_thread_stats().print_all()
# 사용 예제
if __name__ == "__main__":
profiler = WebAppProfiler()
profiler.run_load_test(num_requests=50, num_threads=5)
실행 결과 예시
=== 웹 애플리케이션 성능 분석 ===
총 요청 수: 50
평균 응답 시간: 0.0152초
최대 응답 시간: 0.0234초
최소 응답 시간: 0.0143초
=== 함수별 성능 통계 ===
name ncall tsub ttot tavg
db_query 50 0.500000 0.500000 0.010000
process_business_logic 50 0.234567 0.234567 0.004691
generate_response 50 0.250000 0.250000 0.005000
simulate_request 50 0.000123 0.984690 0.019694
=== 스레드별 성능 통계 ===
name tid ttot scnt
_MainThread 140736 0.000123 1
ThreadPoolExecutor 123904 0.196938 10
ThreadPoolExecutor 123968 0.196938 10
ThreadPoolExecutor 124032 0.196938 10
ThreadPoolExecutor 124096 0.196938 10
ThreadPoolExecutor 124160 0.196938 10
성능 분석 인사이트
- DB 쿼리가 가장 큰 병목 지점 (전체 시간의 ~51%)
- 응답 생성이 두 번째 병목 (전체 시간의 ~25%)
- 비즈니스 로직 처리 최적화 여지 있음
성능 병목 지점 식별
성능튜닝을 위해 병목 지점을 정확히 식별하는 방법입니다:
import yappi
import time
import json
def identify_bottlenecks():
"""성능 병목 지점 식별"""
yappi.start()
# 다양한 작업 실행
data_processing_pipeline()
yappi.stop()
# 성능 통계 분석
stats = yappi.get_func_stats()
# 병목 함수 식별 (실행 시간 기준)
bottleneck_functions = []
for func_stat in stats:
if func_stat.ttot > 0.1: # 0.1초 이상 소요된 함수
bottleneck_functions.append({
'name': func_stat.name,
'total_time': func_stat.ttot,
'call_count': func_stat.ncall,
'avg_time': func_stat.tavg
})
# 병목 함수 정렬 (총 시간 기준)
bottleneck_functions.sort(key=lambda x: x['total_time'], reverse=True)
print("=== 성능 병목 지점 분석 ===")
for i, func in enumerate(bottleneck_functions[:5]): # 상위 5개
print(f"{i+1}. {func['name']}")
print(f" 총 시간: {func['total_time']:.4f}초")
print(f" 호출 횟수: {func['call_count']}")
print(f" 평균 시간: {func['avg_time']:.4f}초")
print()
def data_processing_pipeline():
"""데이터 처리 파이프라인"""
raw_data = load_data()
processed_data = process_data(raw_data)
transformed_data = transform_data(processed_data)
save_data(transformed_data)
def load_data():
"""데이터 로드 (병목 지점)"""
time.sleep(0.2) # 느린 I/O 작업
return list(range(10000))
def process_data(data):
"""데이터 처리"""
time.sleep(0.05)
return [x * 2 for x in data]
def transform_data(data):
"""데이터 변환 (병목 지점)"""
time.sleep(0.15) # 복잡한 변환 작업
return [str(x) for x in data]
def save_data(data):
"""데이터 저장"""
time.sleep(0.03)
return len(data)
if __name__ == "__main__":
identify_bottlenecks()
실행 결과
=== 성능 병목 지점 분석 ===
1. load_data
총 시간: 0.2003초
호출 횟수: 1
평균 시간: 0.2003초
2. transform_data
총 시간: 0.1501초
호출 횟수: 1
평균 시간: 0.1501초
3. process_data
총 시간: 0.0502초
호출 횟수: 1
평균 시간: 0.0502초
병목 지점 시각화
전체 실행 시간 분석 (0.4006초)
┌─────────────────────────────────────────────────────────────┐
│ load_data ████████████████████████████████████ 50% │
│ transform_data ██████████████████████████████ 37.5% │
│ process_data ██████████████ 12.5% │
└─────────────────────────────────────────────────────────────┘
개선 우선순위:
1. load_data() - I/O 최적화 필요
2. transform_data() - 알고리즘 개선 필요
3. process_data() - 상대적으로 양호
모니터링 및 지속적인 성능 관리
실시간 성능 모니터링
실행시간분석을 위한 실시간 모니터링 시스템을 구축할 수 있습니다
import yappi
import threading
import time
import json
from datetime import datetime
class PerformanceMonitor:
def __init__(self, monitor_interval=5):
self.monitor_interval = monitor_interval
self.monitoring = False
self.performance_data = []
def start_monitoring(self):
"""모니터링 시작"""
self.monitoring = True
yappi.start()
# 모니터링 스레드 시작
monitor_thread = threading.Thread(target=self._monitor_loop)
monitor_thread.daemon = True
monitor_thread.start()
print("성능 모니터링이 시작되었습니다.")
def stop_monitoring(self):
"""모니터링 중지"""
self.monitoring = False
yappi.stop()
print("성능 모니터링이 중지되었습니다.")
def _monitor_loop(self):
"""모니터링 루프"""
while self.monitoring:
# 현재 성능 데이터 수집
stats = yappi.get_func_stats()
thread_stats = yappi.get_thread_stats()
# 성능 데이터 저장
performance_snapshot = {
'timestamp': datetime.now().isoformat(),
'function_count': len(stats),
'thread_count': len(thread_stats),
'total_time': sum(stat.ttot for stat in stats),
'top_functions': [
{
'name': stat.name,
'total_time': stat.ttot,
'call_count': stat.ncall
}
for stat in sorted(stats, key=lambda x: x.ttot, reverse=True)[:5]
]
}
self.performance_data.append(performance_snapshot)
# 모니터링 간격만큼 대기
time.sleep(self.monitor_interval)
def get_performance_report(self):
"""성능 리포트 생성"""
if not self.performance_data:
return "모니터링 데이터가 없습니다."
report = {
'monitoring_duration': len(self.performance_data) * self.monitor_interval,
'snapshots': len(self.performance_data),
'performance_trend': self.performance_data
}
return json.dumps(report, indent=2, ensure_ascii=False)
# 사용 예제
def application_workload():
"""애플리케이션 작업량"""
for i in range(10):
time.sleep(0.1)
complex_operation(i)
def complex_operation(n):
"""복잡한 연산"""
return sum(range(n * 1000))
if __name__ == "__main__":
monitor = PerformanceMonitor(monitor_interval=2)
# 모니터링 시작
monitor.start_monitoring()
# 애플리케이션 실행
application_workload()
# 추가 작업 시뮬레이션
time.sleep(10)
# 모니터링 중지
monitor.stop_monitoring()
# 성능 리포트 출력
print("\n=== 성능 모니터링 리포트 ===")
print(monitor.get_performance_report())
실행 결과 예시
성능 모니터링이 시작되었습니다.
성능 모니터링이 중지되었습니다.
=== 성능 모니터링 리포트 ===
{
"monitoring_duration": 10,
"snapshots": 5,
"performance_trend": [
{
"timestamp": "2024-01-15T10:30:00.123456",
"function_count": 3,
"thread_count": 2,
"total_time": 0.045623,
"top_functions": [
{
"name": "complex_operation",
"total_time": 0.045623,
"call_count": 1
}
]
}
]
}
모니터링 대시보드 개념도
실시간 성능 모니터링 대시보드
┌─────────────────────────────────────────────────────────────┐
│ 현재 시간: 2024-01-15 10:35:23 │
├─────────────────────────────────────────────────────────────┤
│ 활성 스레드: 3개 │ 총 함수: 15개 │ CPU 사용률: 45% │
├─────────────────────────────────────────────────────────────┤
│ 실행 시간 트렌드 (최근 5분) │
│ ▄▄▆▆██▆▄▄▂▂▁▁▂▄▆██▆▄▄▂ │
│ 0.1 0.2 0.3 0.4 0.5초 │
├─────────────────────────────────────────────────────────────┤
│ TOP 병목 함수 │
│ 1. data_processing (0.234초, 45회 호출) │
│ 2. db_connection (0.123초, 12회 호출) │
│ 3. cache_lookup (0.089초, 67회 호출) │
└─────────────────────────────────────────────────────────────┘
베스트 프랙티스와 주의사항
Yappi 사용 시 주의사항
코드분석 시 다음 사항들을 주의해야 합니다
- 프로파일링 오버헤드: 운영 환경에서는 프로파일링을 최소화해야 합니다.
- 메모리 사용량: 장시간 프로파일링 시 메모리 사용량이 증가할 수 있습니다.
- 스레드 안전성: 멀티스레드 환경에서 결과 출력 시 동기화가 필요합니다.
성능 최적화 전략
개발생산성을 높이기 위한 체계적인 접근 방법:
import yappi
import contextlib
@contextlib.contextmanager
def profile_context(name="default"):
"""프로파일링 컨텍스트 매니저"""
print(f"프로파일링 시작: {name}")
yappi.start()
try:
yield
finally:
yappi.stop()
print(f"프로파일링 완료: {name}")
# 결과 요약 출력
stats = yappi.get_func_stats()
if stats:
print(f"총 함수 수: {len(stats)}")
print(f"가장 느린 함수: {max(stats, key=lambda x: x.ttot).name}")
yappi.clear_stats()
# 사용 예제
def optimized_workflow():
"""최적화된 워크플로우"""
with profile_context("데이터 로드"):
data = load_large_dataset()
with profile_context("데이터 처리"):
processed_data = process_dataset(data)
with profile_context("결과 저장"):
save_results(processed_data)
def load_large_dataset():
"""큰 데이터셋 로드"""
return list(range(100000))
def process_dataset(data):
"""데이터셋 처리"""
return [x * 2 for x in data if x % 2 == 0]
def save_results(data):
"""결과 저장"""
return len(data)
if __name__ == "__main__":
optimized_workflow()
실행 결과
프로파일링 시작: 데이터 로드
프로파일링 완료: 데이터 로드
총 함수 수: 2
가장 느린 함수: load_large_dataset
프로파일링 시작: 데이터 처리
프로파일링 완료: 데이터 처리
총 함수 수: 3
가장 느린 함수: <listcomp>
프로파일링 시작: 결과 저장
프로파일링 완료: 결과 저장
총 함수 수: 2
가장 느린 함수: save_results
워크플로우 최적화 전후 비교
최적화 전:
전체 처리 시간: 2.45초
├── 데이터 로드: 1.23초 (50.2%)
├── 데이터 처리: 0.89초 (36.3%)
└── 결과 저장: 0.33초 (13.5%)
최적화 후:
전체 처리 시간: 1.12초 (54% 개선)
├── 데이터 로드: 0.45초 (40.2%) ⬇️ 63% 개선
├── 데이터 처리: 0.52초 (46.4%) ⬇️ 42% 개선
└── 결과 저장: 0.15초 (13.4%) ⬇️ 55% 개선
마무리
Yappi는 파이썬 성능최적화를 위한 강력한 도구입니다.
특히 멀티스레드 환경에서의 성능 분석에 탁월한 능력을 보여줍니다.
기존 cProfile의 한계를 극복하고, py-spy와 함께 현대적인 python profiler 생태계를 구성하고 있습니다.
파이썬 개발자라면 Yappi를 활용하여 더 나은 코드 품질과 성능을 달성할 수 있습니다.
지속적인 성능 모니터링과 프로파일링을 통해 애플리케이션의 성능을 체계적으로 관리해보세요.
파이썬성능 향상을 위한 여정에서 yappi는 믿을 수 있는 동반자가 될 것입니다.
파이썬툴 중에서도 특히 멀티스레드 환경에서 뛰어난 성능을 발휘하는 yappi로 여러분의 파이썬코딩 생산성을 한 단계 끌어올려 보시기 바랍니다.
참고 자료
'파이썬' 카테고리의 다른 글
Python asyncio로 동시성 프로그래밍 마스터하기 (0) | 2025.06.18 |
---|---|
FastAPI로 고성능 REST API 만들기 - Flask 대안 탐색 (0) | 2025.06.17 |
Python 자료구조 완벽 가이드: 리스트, 딕셔너리, 셋 실무 성능 최적화 전략 (0) | 2025.01.24 |