파이썬

Yappi란? 파이썬 코드 성능 분석을 위한 Yappi 프로파일러 사용법과 예제

devcomet 2025. 7. 16. 15:29
728x90
반응형

Yappi Python profiler tutorial thumbnail with performance analysis graphics
Yappi란? 파이썬 코드 성능 분석을 위한 Yappi 프로파일러 사용법과 예제 썸네일

 

Yappi는 파이썬 멀티스레드 애플리케이션의 성능 분석과 프로파일링을 위한 강력한 도구로,

기존 cProfile의 한계를 극복하고 실시간 성능 최적화를 가능하게 합니다.


파이썬 개발자라면 누구나 한 번쯤은 코드 성능에 대한 고민을 해봤을 것입니다.

특히 복잡한 애플리케이션이나 멀티스레드 환경에서의 성능 분석은 더욱 까다로운 작업입니다.

이때 Yappi(Yet Another Python Profiler)는 파이썬 개발자들에게 강력한 해결책을 제공합니다.


Yappi 프로파일러란 무엇인가?

Yappi는 파이썬 코드의 성능을 분석하는 전문 python profiler입니다.

기존의 cProfile과 달리 멀티스레드 환경에서도 정확한 성능 분석이 가능하며, 실시간으로 프로파일링 데이터를 수집할 수 있습니다.

파이썬 yappi는 C로 작성된 확장 모듈로, 낮은 오버헤드로 높은 성능을 제공합니다.

Yappi의 주요 특징

  • 멀티스레드 지원: 각 스레드별로 독립적인 프로파일링 데이터 수집
  • 실시간 분석: 프로그램 실행 중에도 프로파일링 데이터 확인 가능
  • 낮은 오버헤드: C로 구현되어 성능 영향 최소화
  • 유연한 출력 형식: 다양한 포맷으로 결과 출력 지원

appi profiler architecture diagram showing components and data flow
Yappi 아키텍처 다이어그램

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를 고려해볼 수 있습니다.

Performance comparison chart between Yappi, cProfile, and py-spy profilers
프로파일러 비교 차트

 

시각적 성능 비교 다이어그램

멀티스레드 지원 비교
┌─────────────┬──────────┬──────────┬──────────┐
│ 프로파일러   │  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() - 상대적으로 양호

 

Performance bottleneck analysis chart showing function execution times and call hierarchy
성능 병목 지점 분석 결과 차트


모니터링 및 지속적인 성능 관리

실시간 성능 모니터링

실행시간분석을 위한 실시간 모니터링 시스템을 구축할 수 있습니다

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 사용 시 주의사항

코드분석 시 다음 사항들을 주의해야 합니다

  1. 프로파일링 오버헤드: 운영 환경에서는 프로파일링을 최소화해야 합니다.
  2. 메모리 사용량: 장시간 프로파일링 시 메모리 사용량이 증가할 수 있습니다.
  3. 스레드 안전성: 멀티스레드 환경에서 결과 출력 시 동기화가 필요합니다.

성능 최적화 전략

개발생산성을 높이기 위한 체계적인 접근 방법:

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로 여러분의 파이썬코딩 생산성을 한 단계 끌어올려 보시기 바랍니다.


참고 자료

728x90
반응형