성능 테스트 설계 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

1. 성능 테스트 설계 요약

a. 테스트 목적

  • 모델이 이미지 100장을 처리할 때의 실행 시간, CPU 사용량, **메모리 사용량, (GPU 사용량)**을 측정
  • 대상 모델을 CPU 환경에서의 실제 서빙 성능 관점에서 평가

b. 측정 환경

  • 기기 사양: Apple M2 Pro(10 코어 (6 성능 및 4 효율)), RAM 16GB
  • 실행 방식: Python3로 실행되는 서브프로세스(subprocess.Popen) 기반 실행
    • 메인 프로세스와 분리된 리소스 사용량 측정을 위함
    • 측정 신뢰성을 높이기 위한 구조
  • 측정 시작 시점을 사용자 입력으로 수동 제어
    • 모델 로딩, 초기화, 데이터 준비 등 사전 처리 과정에서 발생하는 리소스 소비를 측정 대상에서 제외
    • 실제 서비스에서 중요한 "모델 추론(inference)" 구간의 리소스 사용량만 정확히 측정

c. 측정 방식

  • 실행 시간: time.time()으로 시작~종료 시간 차이 계산
  • CPU 사용량: psutil을 이용하여 서브프로세스의 cpu_percent를 0.1초 간격으로 샘플링
  • 메모리 사용량: RSS 기반 memory_info().rss를 MB 단위로 측정
  • GPU 사용량(사용 시):
    • GPUtil.getGPUs()를 통해 GPU 상태를 주기적으로 조회
    • GPU 연산 부하율 (load): 0.0 ~ 1.0 범위 값을 백분율(%)로 변환하여 GPU 사용률 계산
    • GPU 메모리 사용량 (memoryUsed / memoryTotal): 현재 사용 중인 메모리와 전체 메모리(MB)를 함께 출력

d. 모니터링 구조

  • monitor_subprocess(pid): 주어진 프로세스 ID에 대해 주기적으로 CPU/메모리 사용량 수집
  • Thread를 이용한 비동기 모니터링: 메인 프로세스가 기다리는 동안 백그라운드로 리소스 모니터링 수행

e. 평균 계산 방식

  • 총 5회 테스트 반복
  • 각 반복에서의 평균 CPU, 최대 CPU, 평균 메모리, 최대 메모리, 실행 시간 등을 수집 후 종합

2. 성능 테스트 코드

  • CPU, RAM

    import time
    import psutil
    import subprocess
    import threading
    
    # 주어진 프로세스 ID(pid)를 주기적으로 모니터링하여
    # CPU 사용량 (%)과 메모리 사용량 (MB)을 기록하는 함수
    def monitor_subprocess(pid, interval=0.1):
        try:
            proc = psutil.Process(pid)  # 해당 PID의 프로세스 객체 가져오기
        except psutil.NoSuchProcess:
            return [], []  # 프로세스가 없으면 빈 리스트 반환
    
        # 초기 CPU 측정값 세팅 (baseline 설정, 첫 호출은 항상 0)
        proc.cpu_percent(interval=None)
    
        cpu_usages = []  # CPU 사용량 기록 리스트
        mem_usages = []  # 메모리 사용량 기록 리스트
    
        # 프로세스가 실행 중인 동안 반복
        while proc.is_running():
            try:
                # interval(기본 0.1초)마다 CPU 사용률 측정
                cpu = proc.cpu_percent(interval=interval)
                # RSS(Resident Set Size)를 통해 메모리 사용량 측정 (단위: MB)
                mem = proc.memory_info().rss / (1024 ** 2)
    
                # 수집된 값이 유효하면 리스트에 추가
                if cpu is not None:
                    cpu_usages.append(cpu)
                if mem is not None:
                    mem_usages.append(mem)
    
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                break  # 프로세스가 종료되었거나 접근 권한이 없을 경우 종료
    
        return cpu_usages, mem_usages
    
    # 측정 대상 Python 스크립트를 실행하고 모니터링하는 메인 함수
    def run_and_monitor(script_path):
        print(f"🔧 Running script: {script_path}")
    
        # subprocess를 통해 별도 프로세스로 스크립트 실행
        process = subprocess.Popen(["python3", script_path])
    
        # 모니터링 시작 전에 1초 대기 (프로세스 안정화 목적)
        time.sleep(1)
    
        # 수동으로 측정 시작 시점을 지정할 수 있도록 대기
        # → 모델 로딩 등 초기화 과정에서 발생하는 리소스 사용은 제외하고, 순수한 "추론(inference)" 구간에 대한 CPU/RAM 측정을 위함
        input('측정 시작 전 Enter 키를 누르세요')
        start_time = time.time()  # 측정 시작 시간 기록
        result = {}  # 모니터링 결과 저장 딕셔너리
    
        # 모니터링 스레드 함수 정의
        def monitor():
            result['cpu'], result['mem'] = monitor_subprocess(process.pid)
    
        # 모니터링 스레드 실행 (main과 병렬)
        monitor_thread = threading.Thread(target=monitor)
        monitor_thread.start()
    
        process.wait()  # subprocess가 종료될 때까지 대기
        monitor_thread.join()  # 모니터링 스레드 종료까지 대기
    
        end_time = time.time()  # 측정 종료 시간 기록
    
        elapsed = end_time - start_time  # 전체 실행 시간 계산
    
        # 결과 리스트에서 평균 및 최대값 계산
        cpu_usages = result.get('cpu', [])
        mem_usages = result.get('mem', [])
    
        avg_cpu = sum(cpu_usages) / len(cpu_usages) if cpu_usages else 0
        max_cpu = max(cpu_usages) if cpu_usages else 0
        avg_mem = sum(mem_usages) / len(mem_usages) if mem_usages else 0
        max_mem = max(mem_usages) if mem_usages else 0
    
        # 최종 결과 출력
        print(f"\nElapsed Time: {elapsed:.2f} seconds")
        print(f"CPU Usage - Average: {avg_cpu:.2f}% | Peak: {max_cpu:.2f}%")
        print(f"Memory Usage - Average: {avg_mem:.2f} MB | Peak: {max_mem:.2f} MB")
    
    # 예시 실행: clip_test.py 파일에 대해 성능 측정 수행
    run_and_monitor("clip_test.py")
    
  • GPU

    import time
    import shutil
    import os
    
    # GPU 정보 수집을 위한 GPUtil 모듈 임포트 (없으면 자동 설치)
    try:
        import GPUtil
    except ImportError:
        os.system("pip install -q GPUtil")  # 조용히 설치
        import GPUtil
    
    # GPU 상태를 주기적으로 모니터링하는 함수
    def monitor_gpu(interval=10):
        while True:
            try:
                # 시스템에 연결된 GPU 목록을 가져옴 (여러 개일 수 있음)
                gpus = GPUtil.getGPUs()
    
                if gpus:
                    # 첫 번째 GPU 정보만 사용
                    gpu = gpus[0]
    
                    # GPU 이름 (예: NVIDIA RTX 3090)
                    name = gpu.name
    
                    # GPU 사용률 (0.0 ~ 1.0, 백분율로 변환)
                    load = gpu.load * 100
    
                    # GPU 메모리 사용량 (단위: MB)
                    used_memory = gpu.memoryUsed
                    total_memory = gpu.memoryTotal
    
                    # 결과 출력
                    print(
                        f"[GPU MONITOR] {name} | Load: {load:.1f}% | "
                        f"Memory: {used_memory}/{total_memory} MB"
                    )
                else:
                    print("[GPU MONITOR] No GPU detected.")
    
            except Exception as e:
                print(f"[GPU MONITOR] Error occurred: {e}")
    
            # 지정된 시간 간격 후 다시 측정
            time.sleep(interval)
    
    # 예시 실행 (10초 간격으로 GPU 상태 출력)
    monitor_gpu(interval=10)