▶ Non Functional Requirement
- "성능 측정(performance estimation) 혹은 프로파일링(profiling)"은 서비스 혹은 어플리케이션이 '실용적인지 여부'를 판단하는 기준이 된다. 아무로 기능 면에서 좋은 소프트웨어라고 할지라도 필요한 시스템 사양(specification)이 과다하다면 써먹을 수 없다. 혹은 실행 속도가 느리다면 환영 받지 못할 것이다.
- 성능 측정을 모든 기능을 개발한 후에 수행하게 되면 2가지 문제가 발생할 수 있다.
- 성능 문제를 해결하는데 필요한 시간이 부족해서, 최초에 수립한 일정을 지키기 못하거나,
- 문제를 해결할 수 없어서 실패한 프로젝트가 되고 마는 것이다.
- 따라서, 성능 측정 준비는 기능 설계를 진행하면서 함께 준비하고, 기능 구현 과정 중에 몇 차례에 걸쳐 성능을 측정함으로써 중간 과정에서 취약점(weak point)가 어느 부분인지, 어떤 기능으로 인해 급작스럽게 성능이 하락하는지 검사하는 것이 현명하다.
▶ 성능에 대한 핵심 개념과 사전 지식
- "성능(performance)"를 좌우하는 2가지 요소는 "공간"과 "시간"이다. 프로그래밍에서 "공간"이라는 개념은 프로그램이 소모하는 메모리(작게는 메인 메모리, 크게 보면 디스크와 네트워크)이며, "시간" 개념은 얼마나 CPU 자원을 오래 사용하는가 이다.
- 따라서, 소프트웨어 성능은 "메모리 사용량(공간")과 "CPU 사용량(시간)" 등 2가지 측정 지표(metrics)가 존재한다.
- 개발자가 작성하는 "코드"가 얼마나 많은 "공간"과 "시간"을 필요로 하는가에 대해서는 "알고리즘"과 "자료구조" 지식을 통해 이론적으로 예측할 수는 있으나, 다음과 같은 조건에 따라 "같은 문제"라고 하더라도 실질적으로 상당한 차이가 발생한다.
- 어떤 언어로 개발하는가?
: 하위 수준 언어일수록 빠르고, 컴파일러(compiler) 방식의 언어가 인터프리터(interpreter) 언어보다 빠르다. - 개발자가 구체적으로 어떤 방법으로 구현했는가?
: 구현 방식은 API 호출, 라이브러리 활용, 직접 구현 등 방식을 한정할 수 없다. - 어떤 컴파일러를 사용해서 컴파일 했는다?
: 컴파일러에 따라서 기계어를 생성하는 로직이 다르다. 컴파일러 자체에 성능 최적화 기능이 포함되어 있는 경우도 많다. - 어떤 머신(machine)에서 실행하는가?
: 게임과 같은 실수형 계산 및 그래픽 처리를 많이 하는 프로그램은 GPU 성능에 의존하는 경향이 있다. - 위와 같은 이유로 성능은 정적인 "코드"만으로 예측할 수 없기 때문에 실행하면서 측정하는 것이 가장 '정확하다'! 프로그램을 실행하면서 성능을 측정하고, 분석하는 행위를 "프로파일링(profiling)"이라고 하며, 프로그램의 성능을 측정하는 도구를 "프로파일러(profiler)"라고 한다. 프로그램의 성능을 측정한 후, 개선하는 작업을 "최적화(optimizing)"라고 한다.
▶ 성능에 대한 정의와 이해 at slideshare.net
▶ DIY Profiling at slideshare.net
▶ 성능 측정 유형
- CPU 측정
- Times/ Calls
: 함수 혹은 메소드가 얼마나 오랜동안 CPU를 사용하는가 혹은 얼마나 많이 호출되는가를 측정하는 것이다. - Sampling
: 아주 짧은 시간동안 호출되는 함수의 수행 시간을 측정하면 해당 함수의 수행 시간을 왜곡하게 된다. (함수가 수행되는 시간보다 더 많은 시간을 측정하는데 소모하거나, 수행시간이 느린 것으로 잘못 판단할 수 있다.) 혹은 초당 10만번 이상 호출되는 함수들을 측정하고자 할 경우 샘플링(sampling) 기법을 사용한다. 샘플링 기법은 프로그램 수행 중 일정 주기 마다 현재 실행 상태 (실행 중인 위치 정보)를 기록하는 기법이다. - 메모리 측정
- Usage
: 메모리를 얼마나 "자주" 할당 및 해제하는가를 측정한다. 메모리의 할당 및 해제는 비용이 많이 드는 '비싼' 작업이다. - Allocation
: 메모리를 얼마나 "많이" 할당하느냐를 측정한다. 메모리를 과다하게 할당할 경우 "메모리 부족(Out of memory)" 오류를 발생 시키거나, 스왑(swap) 메모리를 사용하게 되어 급격한 성능 저하를 유발하게 된다.
▶ 스스로 하는 성능 측정 (DIY Java Profiling)
- 앞서 Romon Elizrov 의 자료에서 제시하는 프로파일링 기법들은 다음과 같다.
- 자바 코드 내에서 성능 측정 코드 삽입 (Just code it in Java)
: 개발자가 직접 성능을 측정하는 코드를 자바 코드 내에 삽입하는 방법이다. - 자바 가상 머신의 기능 활용 (Know your JVM features)
: JVM 에서는 메모리 사용 상태 및 스레드(thread)의 실행 상태를 확인할 수 있는 기능(옵션)이 제공된다. - 바이트 코드 조작 활용 (Use byte code manipulation)
: 자신이 작성한 프로그램이 아니거가 - 소스 코드가 없다거나 -, 이미 완성된 프로그램의 성능을 측정하고자 하는 경우에 사용되는 기법이다. 컴파일된 클래스 파일 (확장자가 .class)을 조작해서 성능 로그를 생성하는 코드를 삽입하는 기법이며, 자칫 JVM의 비정상 종료(crush)를 유발할 수 있기 때문에 조심스럽게 적용해야 한다.
▶ 프로파일링 클래스 구현
- docar.util.profile.Profiler
: 성능 측정 정보를 기록하는 싱글턴(singleton) 패턴의 클래스. addCallEvent() 메소드를 호출할 때마다, 특정 기능의 수행 횟수 및 누적 수행 시간을 기록한다. - docar.util.profile.StopWatch
: 특정 기능(메소드, 클래스)의 수행 시간을 측정하는 클래스. - docar.uti.Report
: 성능 측정 결과를 출력하는 클래스. "기능 명칭 및 호출 횟수", "총 수행 시간", "평균 수행 시간" 등을 출력한다. - 사용 방법 예시
// 메소드 시작 부분에서 "기능 명칭"을 인자로 스톱워치 생성 StopWatch stopWatch = new StopWatch("DuplicationFinder.main"); // 메소드 종료 부분에서 수행 시간 기록(누적) Profiler.getInstance().addCallEvent(stopWatch.stop()); // 수행 시간 측정 결과 출력 Report.report(System.out);
측정 결과 예시
- 이클립스 프로젝트 (소스 다운로드)