앱인토스 개발자센터 로고
Skip to content

성능 최적화 평가 기준

AppsInToss Unity 미니앱의 성능을 정확하게 측정하고 평가할 수 있는 기준과 도구를 소개해요.
정확한 측정이 이루어져야, 그다음 단계인 최적화 전략도 제대로 세울 수 있어요.


1. 성능 측정 기준

핵심 성능 지표 (KPI)

🎯 중요도별 성능 지표
├── Critical (치명적 - 즉시 해결 필요)
│   ├── 시작 시간: > 5초
│   ├── 평균 FPS: < 20
│   ├── 메모리 사용량: > 300MB
│   └── 크래시율: > 1%

├── High (높음 - 우선 해결)
│   ├── 시작 시간: 3-5초
│   ├── 평균 FPS: 20-30
│   ├── 메모리 사용량: 200-300MB
│   └── 로딩 시간: > 10초

└── Medium (보통 - 점진적 개선)
    ├── 시작 시간: 2-3초
    ├── 평균 FPS: 30-50
    ├── 메모리 사용량: 150-200MB
    └── 배터리 효율: 네이티브 대비 120%

플랫폼별 성능 기준

iOS 기준

기기 등급시작 시간평균 FPS메모리배터리
High-end (iPhone 13+)< 2초60+< 150MB95%
Mid-range (iPhone 11–12)< 3초50+< 200MB90%
Low-end (iPhone X 이하)< 5초30+< 250MB85%

Android 기준

기기 등급시작 시간평균 FPS메모리배터리
High-end (Flagship)< 2초50+< 150MB90%
Mid-range (중급형)< 4초40+< 200MB85%
Low-end (저가형)< 6초25+< 250MB80%

2. 성능 측정 도구

Unity Built-in 측정 도구

c#
using UnityEngine;
using UnityEngine.Profiling;
using System.Collections.Generic;
using System.Text;

public class AppsInTossPerformanceMeasure : MonoBehaviour
{
    [Header("측정 설정")]
    public bool enableMeasurement = true;
    public float measureInterval = 1.0f;
    public int maxSampleCount = 300; // 5분간 데이터
    
    [Header("성능 임계값")]
    public float fpsWarningThreshold = 30f;
    public float memoryWarningThreshold = 200f; // MB
    public float startupTimeLimit = 5f; // 초
    
    private Dictionary<string, PerformanceMetric> metrics = new Dictionary<string, PerformanceMetric>();
    private float startTime;
    private bool startupMeasured = false;
    
    [System.Serializable]
    public class PerformanceMetric
    {
        public string name;
        public Queue<float> samples = new Queue<float>();
        public float currentValue;
        public float averageValue;
        public float minValue = float.MaxValue;
        public float maxValue = float.MinValue;
        
        public void AddSample(float value)
        {
            samples.Enqueue(value);
            if (samples.Count > 300) samples.Dequeue(); // 최대 샘플 수 유지
            
            currentValue = value;
            minValue = Mathf.Min(minValue, value);
            maxValue = Mathf.Max(maxValue, value);
            
            // 평균 계산
            float sum = 0;
            foreach (float sample in samples)
                sum += sample;
            averageValue = sum / samples.Count;
        }
    }
    
    void Awake()
    {
        startTime = Time.realtimeSinceStartup;
        InitializeMetrics();
    }
    
    void Start()
    {
        if (enableMeasurement)
        {
            InvokeRepeating(nameof(MeasurePerformance), 0f, measureInterval);
        }
    }
    
    void InitializeMetrics()
    {
        metrics["FPS"] = new PerformanceMetric { name = "FPS" };
        metrics["Memory"] = new PerformanceMetric { name = "Memory (MB)" };
        metrics["DrawCalls"] = new PerformanceMetric { name = "Draw Calls" };
        metrics["Triangles"] = new PerformanceMetric { name = "Triangles" };
        metrics["CPUFrameTime"] = new PerformanceMetric { name = "CPU Frame Time (ms)" };
        metrics["GPUFrameTime"] = new PerformanceMetric { name = "GPU Frame Time (ms)" };
    }
    
    void MeasurePerformance()
    {
        // FPS 측정
        float fps = 1.0f / Time.unscaledDeltaTime;
        metrics["FPS"].AddSample(fps);
        
        // 메모리 측정 (MB)
        float memoryMB = Profiler.GetTotalAllocatedMemory(false) / (1024f * 1024f);
        metrics["Memory"].AddSample(memoryMB);
        
        // 렌더링 통계
        metrics["DrawCalls"].AddSample(UnityEngine.Rendering.FrameDebugger.enabled ? 
            UnityEngine.Rendering.FrameDebugger.GetFrameEventCount() : 0);
        
        // CPU/GPU 프레임 시간
        float cpuTime = Time.deltaTime * 1000f; // ms로 변환
        metrics["CPUFrameTime"].AddSample(cpuTime);
        
        // 시작 시간 측정 (한 번만)
        if (!startupMeasured && Time.realtimeSinceStartup > 1f)
        {
            float startupTime = Time.realtimeSinceStartup - startTime;
            LogStartupTime(startupTime);
            startupMeasured = true;
        }
        
        // 경고 임계값 체크
        CheckPerformanceWarnings();
    }
    
    void CheckPerformanceWarnings()
    {
        // FPS 경고
        if (metrics["FPS"].currentValue < fpsWarningThreshold)
        {
            Debug.LogWarning($"Low FPS detected: {metrics["FPS"].currentValue:F1}");
            SendWarningToAppsInToss("low_fps", metrics["FPS"].currentValue);
        }
        
        // 메모리 경고
        if (metrics["Memory"].currentValue > memoryWarningThreshold)
        {
            Debug.LogWarning($"High memory usage: {metrics["Memory"].currentValue:F1}MB");
            SendWarningToAppsInToss("high_memory", metrics["Memory"].currentValue);
        }
    }
    
    void LogStartupTime(float startupTime)
    {
        Debug.Log($"[Performance] Startup time: {startupTime:F2}s");
        
        if (startupTime > startupTimeLimit)
        {
            Debug.LogWarning($"Startup time exceeds limit: {startupTime:F2}s > {startupTimeLimit}s");
        }
        
        // AppsInToss 분석 시스템에 전송
        SendMetricToAppsInToss("startup_time", startupTime);
    }
    
    void SendWarningToAppsInToss(string warningType, float value)
    {
        var data = new Dictionary<string, object>
        {
            {"warning_type", warningType},
            {"value", value},
            {"timestamp", System.DateTime.UtcNow.ToString("o")},
            {"device_model", SystemInfo.deviceModel},
            {"memory_size", SystemInfo.systemMemorySize},
            {"processor_type", SystemInfo.processorType}
        };
        
        // AppsInToss Native 호출
        Application.ExternalCall("SendPerformanceWarning", JsonUtility.ToJson(data));
    }
    
    void SendMetricToAppsInToss(string metricName, float value)
    {
        var data = new Dictionary<string, object>
        {
            {"metric_name", metricName},
            {"value", value},
            {"timestamp", System.DateTime.UtcNow.ToString("o")}
        };
        
        Application.ExternalCall("SendPerformanceMetric", JsonUtility.ToJson(data));
    }
    
    // 성능 리포트 생성
    public string GeneratePerformanceReport()
    {
        var report = new StringBuilder();
        report.AppendLine("=== AppsInToss 성능 측정 리포트 ===");
        report.AppendLine($"측정 시간: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}");
        report.AppendLine($"기기 정보: {SystemInfo.deviceModel}");
        report.AppendLine($"메모리: {SystemInfo.systemMemorySize}MB");
        report.AppendLine($"프로세서: {SystemInfo.processorType}");
        report.AppendLine();
        
        foreach (var metric in metrics.Values)
        {
            report.AppendLine($"[{metric.name}]");
            report.AppendLine($"  현재값: {metric.currentValue:F2}");
            report.AppendLine($"  평균값: {metric.averageValue:F2}");
            report.AppendLine($"  최소값: {metric.minValue:F2}");
            report.AppendLine($"  최대값: {metric.maxValue:F2}");
            report.AppendLine($"  샘플 수: {metric.samples.Count}");
            report.AppendLine();
        }
        
        return report.ToString();
    }
    
    // GUI 성능 표시
    void OnGUI()
    {
        if (!enableMeasurement) return;
        
        GUILayout.BeginArea(new Rect(10, 10, 300, 150));
        GUILayout.Box("AppsInToss 성능 모니터");
        
        GUILayout.Label($"FPS: {metrics["FPS"].currentValue:F1} (평균: {metrics["FPS"].averageValue:F1})");
        GUILayout.Label($"메모리: {metrics["Memory"].currentValue:F1}MB");
        GUILayout.Label($"Draw Calls: {metrics["DrawCalls"].currentValue:F0}");
        
        // 성능 상태 표시
        if (metrics["FPS"].currentValue < fpsWarningThreshold)
        {
            GUI.color = Color.red;
            GUILayout.Label("⚠ FPS 경고");
        }
        
        if (metrics["Memory"].currentValue > memoryWarningThreshold)
        {
            GUI.color = Color.red;
            GUILayout.Label("⚠ 메모리 경고");
        }
        
        GUI.color = Color.white;
        GUILayout.EndArea();
    }
}

3. 자동화된 성능 테스트

성능 테스트 스크립트

c#
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class AutomatedPerformanceTest : MonoBehaviour
{
    [System.Serializable]
    public class TestScenario
    {
        public string name;
        public string sceneName;
        public float testDuration = 60f; // 초
        public int targetFPS = 30;
        public float maxMemoryMB = 200f;
    }
    
    public TestScenario[] testScenarios;
    
    private PerformanceTestResult currentResult;
    
    [System.Serializable]
    public class PerformanceTestResult
    {
        public string scenarioName;
        public float averageFPS;
        public float minFPS;
        public float maxMemoryMB;
        public float testDuration;
        public bool passed;
        public List<string> issues = new List<string>();
    }
    
    public void RunAllTests()
    {
        StartCoroutine(RunTestSequence());
    }
    
    IEnumerator RunTestSequence()
    {
        var allResults = new List<PerformanceTestResult>();
        
        foreach (var scenario in testScenarios)
        {
            Debug.Log($"Starting test: {scenario.name}");
            
            yield return StartCoroutine(RunSingleTest(scenario));
            
            allResults.Add(currentResult);
            
            // 테스트 간 쿨다운
            yield return new WaitForSeconds(2f);
        }
        
        GenerateTestReport(allResults);
    }
    
    IEnumerator RunSingleTest(TestScenario scenario)
    {
        currentResult = new PerformanceTestResult
        {
            scenarioName = scenario.name,
            testDuration = scenario.testDuration
        };
        
        // 씬 로드
        var loadOp = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(scenario.sceneName);
        yield return loadOp;
        
        // 안정화 대기
        yield return new WaitForSeconds(2f);
        
        // 성능 측정 시작
        float startTime = Time.time;
        float fpsSum = 0f;
        int fpsCount = 0;
        float minFPS = float.MaxValue;
        float maxMemory = 0f;
        
        while (Time.time - startTime < scenario.testDuration)
        {
            float fps = 1.0f / Time.unscaledDeltaTime;
            fpsSum += fps;
            fpsCount++;
            minFPS = Mathf.Min(minFPS, fps);
            
            float memoryMB = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemory(false) / (1024f * 1024f);
            maxMemory = Mathf.Max(maxMemory, memoryMB);
            
            yield return new WaitForSeconds(0.1f); // 10Hz 측정
        }
        
        // 결과 계산
        currentResult.averageFPS = fpsSum / fpsCount;
        currentResult.minFPS = minFPS;
        currentResult.maxMemoryMB = maxMemory;
        
        // 성능 기준 체크
        EvaluateTestResult(scenario);
    }
    
    void EvaluateTestResult(TestScenario scenario)
    {
        currentResult.passed = true;
        currentResult.issues.Clear();
        
        if (currentResult.averageFPS < scenario.targetFPS)
        {
            currentResult.passed = false;
            currentResult.issues.Add($"평균 FPS 부족: {currentResult.averageFPS:F1} < {scenario.targetFPS}");
        }
        
        if (currentResult.minFPS < scenario.targetFPS * 0.7f) // 30% 드롭 허용
        {
            currentResult.passed = false;
            currentResult.issues.Add($"최소 FPS 부족: {currentResult.minFPS:F1}");
        }
        
        if (currentResult.maxMemoryMB > scenario.maxMemoryMB)
        {
            currentResult.passed = false;
            currentResult.issues.Add($"메모리 사용량 초과: {currentResult.maxMemoryMB:F1}MB > {scenario.maxMemoryMB}MB");
        }
    }
    
    void GenerateTestReport(List<PerformanceTestResult> results)
    {
        var report = new System.Text.StringBuilder();
        report.AppendLine("=== 자동화 성능 테스트 결과 ===");
        report.AppendLine($"테스트 일시: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}");
        report.AppendLine($"기기: {SystemInfo.deviceModel}");
        report.AppendLine();
        
        int passedCount = 0;
        
        foreach (var result in results)
        {
            report.AppendLine($"[{result.scenarioName}] - {(result.passed ? "PASS" : "FAIL")}");
            report.AppendLine($"  평균 FPS: {result.averageFPS:F1}");
            report.AppendLine($"  최소 FPS: {result.minFPS:F1}");
            report.AppendLine($"  최대 메모리: {result.maxMemoryMB:F1}MB");
            
            if (!result.passed)
            {
                foreach (var issue in result.issues)
                {
                    report.AppendLine($"  ❌ {issue}");
                }
            }
            else
            {
                passedCount++;
            }
            
            report.AppendLine();
        }
        
        report.AppendLine($"전체 결과: {passedCount}/{results.Count} 통과");
        
        Debug.Log(report.ToString());
        
        // AppsInToss 리포팅 시스템에 전송
        SendTestReportToAppsInToss(report.ToString(), results);
    }
    
    void SendTestReportToAppsInToss(string report, List<PerformanceTestResult> results)
    {
        var data = new Dictionary<string, object>
        {
            {"report", report},
            {"results", results},
            {"device_info", new Dictionary<string, object>
            {
                {"model", SystemInfo.deviceModel},
                {"memory", SystemInfo.systemMemorySize},
                {"processor", SystemInfo.processorType},
                {"graphics", SystemInfo.graphicsDeviceName}
            }}
        };
        
        Application.ExternalCall("SendPerformanceTestReport", JsonUtility.ToJson(data));
    }
}

4. 실시간 성능 대시보드

웹 기반 성능 모니터링

tsx
// TypeScript - AppsInToss 성능 대시보드
interface PerformanceData {
    timestamp: number;
    fps: number;
    memory: number;
    drawCalls: number;
    cpuTime: number;
    gpuTime: number;
}

class AppsInTossPerformanceDashboard {
    private performanceData: PerformanceData[] = [];
    private maxDataPoints = 300; // 5분간 데이터 (1초 간격)
    private charts: { [key: string]: any } = {};
    
    constructor() {
        this.initializeDashboard();
        this.startDataCollection();
    }
    
    initializeDashboard() {
        // Chart.js 또는 다른 차트 라이브러리 초기화
        this.createFPSChart();
        this.createMemoryChart();
        this.createDrawCallChart();
    }
    
    createFPSChart() {
        const ctx = document.getElementById('fpsChart') as HTMLCanvasElement;
        this.charts.fps = new Chart(ctx, {
            type: 'line',
            data: {
                labels: [],
                datasets: [{
                    label: 'FPS',
                    data: [],
                    borderColor: '#4CAF50',
                    backgroundColor: 'rgba(76, 175, 80, 0.1)',
                    tension: 0.4
                }]
            },
            options: {
                responsive: true,
                scales: {
                    y: {
                        beginAtZero: true,
                        max: 60,
                        ticks: {
                            callback: function(value) {
                                return value + ' FPS';
                            }
                        }
                    }
                },
                plugins: {
                    legend: {
                        position: 'top'
                    },
                    title: {
                        display: true,
                        text: 'FPS 모니터링'
                    }
                }
            }
        });
    }
    
    createMemoryChart() {
        const ctx = document.getElementById('memoryChart') as HTMLCanvasElement;
        this.charts.memory = new Chart(ctx, {
            type: 'area',
            data: {
                labels: [],
                datasets: [{
                    label: 'Memory (MB)',
                    data: [],
                    borderColor: '#FF9800',
                    backgroundColor: 'rgba(255, 152, 0, 0.2)',
                    fill: true
                }]
            },
            options: {
                responsive: true,
                scales: {
                    y: {
                        beginAtZero: true,
                        ticks: {
                            callback: function(value) {
                                return value + ' MB';
                            }
                        }
                    }
                }
            }
        });
    }
    
    startDataCollection() {
        setInterval(() => {
            this.collectPerformanceData();
        }, 1000); // 1초마다 데이터 수집
    }
    
    collectPerformanceData() {
        // Unity에서 성능 데이터 요청
        if ((window as any).unityInstance) {
            (window as any).unityInstance.SendMessage('AppsInTossPerformanceMeasure', 'SendCurrentMetrics', '');
        }
    }
    
    updateDashboard(data: PerformanceData) {
        // 데이터 저장
        this.performanceData.push(data);
        
        // 최대 데이터 포인트 수 유지
        if (this.performanceData.length > this.maxDataPoints) {
            this.performanceData.shift();
        }
        
        // 차트 업데이트
        this.updateCharts();
        
        // 경고 체크
        this.checkPerformanceAlerts(data);
    }
    
    updateCharts() {
        const labels = this.performanceData.map(d => new Date(d.timestamp).toLocaleTimeString());
        
        // FPS 차트 업데이트
        this.charts.fps.data.labels = labels;
        this.charts.fps.data.datasets[0].data = this.performanceData.map(d => d.fps);
        this.charts.fps.update('none');
        
        // 메모리 차트 업데이트
        this.charts.memory.data.labels = labels;
        this.charts.memory.data.datasets[0].data = this.performanceData.map(d => d.memory);
        this.charts.memory.update('none');
    }
    
    checkPerformanceAlerts(data: PerformanceData) {
        const alerts = [];
        
        if (data.fps < 20) {
            alerts.push({
                type: 'critical',
                message: `심각한 FPS 저하: ${data.fps.toFixed(1)} FPS`
            });
        } else if (data.fps < 30) {
            alerts.push({
                type: 'warning',
                message: `FPS 경고: ${data.fps.toFixed(1)} FPS`
            });
        }
        
        if (data.memory > 200) {
            alerts.push({
                type: 'critical',
                message: `높은 메모리 사용량: ${data.memory.toFixed(1)} MB`
            });
        } else if (data.memory > 150) {
            alerts.push({
                type: 'warning',
                message: `메모리 경고: ${data.memory.toFixed(1)} MB`
            });
        }
        
        if (alerts.length > 0) {
            this.showAlerts(alerts);
        }
    }
    
    showAlerts(alerts: any[]) {
        const alertContainer = document.getElementById('performanceAlerts');
        if (alertContainer) {
            alertContainer.innerHTML = '';
            alerts.forEach(alert => {
                const alertElement = document.createElement('div');
                alertElement.className = `alert alert-${alert.type}`;
                alertElement.textContent = alert.message;
                alertContainer.appendChild(alertElement);
            });
        }
    }
    
    generateReport() {
        if (this.performanceData.length === 0) return;
        
        const latest = this.performanceData[this.performanceData.length - 1];
        const avgFPS = this.performanceData.reduce((sum, d) => sum + d.fps, 0) / this.performanceData.length;
        const maxMemory = Math.max(...this.performanceData.map(d => d.memory));
        
        return {
            currentFPS: latest.fps,
            averageFPS: avgFPS,
            currentMemory: latest.memory,
            maxMemory: maxMemory,
            sampleCount: this.performanceData.length,
            duration: (latest.timestamp - this.performanceData[0].timestamp) / 1000 // 초
        };
    }
}

// Unity에서 호출하는 글로벌 함수
(window as any).UpdatePerformanceData = (dataJson: string) => {
    const data = JSON.parse(dataJson) as PerformanceData;
    dashboard.updateDashboard(data);
};

// 대시보드 인스턴스 생성
const dashboard = new AppsInTossPerformanceDashboard();

5. 성능 평가 체크리스트

출시 전 성능 검증 체크리스트

  • 시작 시간 5초 이내 (모든 대상 기기)
  • 평균 FPS 30 이상 유지 (1분 이상 테스트)
  • 메모리 사용량 200MB 이하 (장시간 플레이)
  • 크래시율 1% 이하 (100명 이상 베타 테스트)
  • 배터리 효율성 네이티브 앱 대비 85% 이상
  • 네트워크 끊김 상황에서 정상 동작
  • 백그라운드 진입/복귀 시 정상 동작
  • 다양한 화면 비율에서 정상 동작
  • 메모리 부족 상황 대응 (우아한 저하)
  • 자동화된 성능 테스트 통과

정기 모니터링 체크리스트

  • 주간 성능 리포트 검토
  • 사용자 피드백 중 성능 관련 이슈 분석
  • 성능 저하 트렌드 모니터링
  • A/B 테스트를 통한 최적화 효과 검증
  • 새로운 기기/OS 버전 호환성 테스트