PHP 애플리케이션이 느려진다면 프로파일러를 사용하여 어디에서 시간이 소비되는지를 파악합니다. 문, 루프, 함수, 클래스, 라이브러리 중 가장 느린 움직임을 보이는 것을 찾을 수 있습니다. 시간 보다는 메모리 사용이 문제가 될 경우에는 좋은 프로파일러가 컴포넌트 풋프린트도 발견해 낼 수 있습니다.
"PHP 애플리케이션을 가장 빠르게" 시리즈의 Part 1에 서는 PHP opcode 캐시(cache)인 XCache를 사용하여 사이트의 속도를 높이는 방법을 설명했다. XCache (캐싱 패키지들 중 하나)는 컴파일 프로세스의 결과를 보존하여 과잉 작업을 줄인다. 페이지가 바뀌지 않는 한, 캐싱 된 페이지는 프록시로서 충분한 역할을 한다. 페이지가 수정되면 캐싱 된 버전은 무효가 되고 다른 것으로 대체된다.
opcode 캐시와 opcode 옵티마이저는 사이트의 반응성을 높일 수 있는 저렴한 기술이라고 할 수 있다. 많은 캐시 패키지들이 프리 및 오픈 소스이기 때문에 코드를 수정할 필요가 없다.
물 론, 어떤 애플리케이션의 경우, PHP 소스 코드의 파일을 상응하는 opcode로 변환하는데 걸리는 시간은 실제 실행 시간과 비교해 본다면 그렇게 많은 시간이 걸리는 것은 아니다. 원격 데이터베이스 서버로 연결하고, 비효율적인 SQL 쿼리로 스토어를 쿼리하고, 데이터를 파싱 및 조작하는데 더 오랜 시간이 걸리며, 비용도 많이 든다. 좋은 네트워크 디자인과 똑똑한 데이터베이스 구현으로 지체된 시간과 느려진 쿼리를 보상할 수 있고, 필요에 따라서 여러분의 동료 전문가에게 의뢰할 수 있다. 하지만, 여러분의 코드가 느려진다면 여러분 스스로 이 문제를 해결해야 한다.
그런데, 어디서부터 시작해야 하는가? 코드가 완성되기 전에 코드를 조작하는 것은 무모한 짓이다. 첫 구현이 적절한 속도로 실행되더라도 마찬가지다. 여러분의 코드가 기능적이고 정확하기는 하지만 느리다면, 첫 번째로 해야 할 것은 성능을 정량화 또는 벤치마킹 하는 것이다. 이 같은 진단 노력 없이 코드를 최적화 한다는 것은 불가능하다.
간단한 성능 메트릭은 wall clock time 또는 페이지에 대한 요청과 완료된 렌더링 간 실제 지연 시간을 측정하는 것이다. 여러분의 워크스테이션에서 웹 서버, 데이터베이스, 브라우저를 로컬로 실행하는 경우, wall clock time이 유익하다. 하지만 wall clock time에는 네트워크 레이턴시, 트래픽에 걸린 활성 웹 서버, 활성 데이터베이스 등이 개입된다.
단일 소스 코드 문을 실행하는데 걸린 시간까지도 측정할 수 있는 훨씬 더 정확한 측정 방법은 코드 프로파일러를 사용하는 것이다. 일반적으로 PHP 런타임 엔진에 대한 확장으로서 구현되는 프로파일러는 문의 시작과 끝 사이의 델타(delta)를 기록하고, 프로시저의 시작과 끝 사이의 델타를 기록하고, 인커밍 요청에 대해 응답을 만드는데 필요한 총 시간을 파악한다. 이를 통해서 가장 느린 움직임을 보이는 문, 루프, 함수, 클래스, 라이브러리를 찾아낼 수 있다. 시간이 문제가 아니고 메모리 사용이 문제일 경우, 좋은 프로파일러라면 컴포넌트 풋프린트 까지도 발견해 낸다.
한 가지 대중적인 PHP용 프로파일러 중 Xdebug가 있다. 서버 후크(hook)를 사용하여 PHP 애플리케이션을 대화식으로 디버깅한다. (자세한 내용은 "더 나은 디버깅 방법"을 참조하라. 본 시리즈에서는 대화식(interactive) 디버깅과 고급 디버깅에 대해서도 다루고 있다.) Xdebug는 소스에서 구현하기 쉽고 Zend 확장으로서 설치된다. (일부 플랫폼에는 바이너리도 사용할 수 있다.) 설치가 되면 PHP 기반 페이지에 대한 각 요청은 KCacheGrind
에서 볼 수 있는 데이터 세트를 생성한다.
여러분이 PHP 유틸리티 phpize
와 php-config
에 액세스 했고, 여러분 시스템의 php.ini 설정 파일에 액세스 했다고 가정한다면, Xdebug를 설치 및 설정하는데 단 몇 분만 소요된다. 아래 제시된 지시 사항은 리눅스® 용이다. 물론 Mac OS X에서도 동일한 단계를 거친다. (Xdebug 웹 사이트에는 Microsoft® Windows®용 Xdebug 버전도 있다.)
Xdebug 최신 버전은 V2.0.0RC3이다. (최종 버전인 V2.0.0도 사용할 수 있다.) tarball을 다운로드 하여 압축을 풀고 소스 코드의 하위 디렉토리로 수정한다. phpize
와 php-config
가 쉘의 PATH에 있는지를 확인하고 phpize
로 구현할 준비를 한다.
Listing 1. Xdebug 설정하기
$ wget http://www.xdebug.org/files/xdebug-2.0.0RC3.tgz $ tar xzf xdebug-2.0.0RC3.tgz $ cd xdebug-2.0.0RC3/xdebug-2.0.0RC3 $ phpize Configuring for: PHP Api Version: 20020918 Zend Module Api No: 20020429 Zend Extension Api No: 20050606 |
phpize
의 결과물은 하나의 스크립트이다. 정확히 말하면 configure이고, 이것은 나머지 빌드 프로세스를 설정한다. Xdebug를 구현하려면, ./configure
를 타이핑 하고, 바로 다음에 make
를 타이핑 한다.
Listing 2. Xdebug 구현하기
$ ./configure checking build system type... i686-apple-darwin8.8.1 checking host system type... i686-apple-darwin8.8.1 checking for egrep... grep -E ... $ make ... Build complete. (It is safe to ignore warnings about tempnam and tmpnam). |
make
명령어는 Xdebug 확장인 xdebug.so를 만들어 낸다. 이제 남은 일은 sudo make install
을 사용하여 설치하는 것이다.
$ sudo make install Installing shared extensions: /usr/lib/php/extensions/no-debug-non-zts-20020429/ |
주: 터미널 윈도우에서 마지막 명령어를 실행했다면, 마지막 단계에서 만들어진 디렉토리를 선택하고 복사한다. 이것은 그 다음 단계에 필요하다.
마지막으로, 프로파일 데이터를 시각화 하려면, KCacheGrind
와 GraphViz
가 있어야 한다. K Desktop Environment (KDE)가 포함된 리눅스 배포판에는KCacheGrind
와 GraphViz
가 이미 있다. 그렇지 않을 경우, 각자 선호하는 리눅스 배포판에 맞는 버전을 찾으면 된다. Debian 사용자들은 Advanced Packaging Tool (APT)을 사용하여 KCacheGrind
, GraphViz
와 모든 패키지의 구성물들을 빠르게 설치할 수 있다.
Listing 3. KCacheGrind 설치하기
$ apt-cache search kcachegrind valgrind-callgrind - call-graph skin for valgrind kcachegrind - visualisation tool for valgrind profiling output kcachegrind-converters - format converters for KCachegrind profiling visualisation tool $ apt-cache search graphviz graphviz - rich set of graph drawing tools graphviz-dev - graphviz Libs and Headers against which to build applications graphviz-doc - additional documentation for graphviz libdeps-renderer-dot-perl - DEPS renderer plugin using GraphViz/dot ... $ sudo apt-get install kcachegrind graphviz ... |
KDE가 시스템에 설치되지 않으면, KCacheGrind
, GraphViz
, 모든 사전 필수 항목들에 약90 MB의 디스크 공간이 필요하다.
|
Xdebug 확장이 설치되었다면, 확장을 실행 및 구성할 준비가 된 것이다. 텍스트 에디터에 php.ini를 열고 다음 라인을 추가한다.
Listing 4. 확장 실행 및 구성
zend_extension = /usr/lib/php/extensions/no-debug-non-zts-20020429/xdebug.so xdebug.profiler_output_dir = "/tmp/xdebug/" xdebug.profiler_enable = Off xdebug.profiler_enable_trigger = 1 |
첫 번째 라인인 zend_extension
은 Xdebug 확장을 로딩한다. 두 번째 라인은 프로파일러 아웃풋을 저장할 디렉토리의 이름을 정한다. 필요할 경우 네임드 디렉토리를 만들고 모드를 수정하여 웹 서버 사용자 쓰기 액세스를 허용한다.
세 번째 라인은 프로파일러를 실행 불가로 만든다. 하지만, 네 번째 라인은 HTTP GET
또는 POST
매개변수 XDEBUG_PROFILE
가 설정될 때마다 프로파일러를 실행시킨다. (프로파일러를 계속해서 실행시키려면, 세 번째 라인의 Off
를 On
으로 바꾼다.)
라인을 추가하고 아웃풋 디렉토리가 쓰기 가능한 것인지를 확인한 후에, 웹 서버를 재시작 한다. 다른 PHP 확장과 마찬가지로, Xdebug가 설치되어 사용할 수 있는지를 확인하려면, 단순한 PHP 프로그램을 만들어서 phpinfo()
를 호출하고 그 결과를 분석해 본다. 그림 1과 비슷한 것을 보게 될 것이다. (단순함을 위해 전체 아웃풋은 뺐다.)
그림 1. Xdebug 설치 여부를 보여주는 Phpinfo
Zend 로고까지 스크롤을 내리는 방법도 있다. Xdebug가 올바르게 로딩되어 설정되었다면 Xdebug가 그 로고 옆에 나타난다.
|
코드를 프로파일링 하려면, 브라우저에서 PHP 애플리케이션으로 간다. 프로파일러가 트리거 당 케이스 별로 실행하도록 설정했다면 URL에 XDEBUG_PROFILE=1
을 붙이거나, 폼 안에 매개변수를 삽입한다.
예를 들어, 간단한 ACME Fibonacci Maker인 fibonacci.php를 프로파일링 한다고 해보자. (Listing 5) XDEBUG_PROFILE
매개변수는 숨겨진 변수에 있는 폼 안에 설정된다. (코드가 실행 환경으로 이동할 때, Xdebug는 실행 불가가 되면서 이 변수는 어떤 해도 입히지 않는다.)
Listing 5. Fibonacci.php
<?php function fib($nth = 1) { if ( $nth < 2 ) { return( $nth ); } return( fib( $nth - 1) + fib( $nth - 2 ) ); } ?> <html> <head> <title>ACME Fibonacci Maker</title> </head> <body> <h2>Try the ACME Fibonacci Maker!</h2> <form action="fibonacci.php" method="POST"> <input type="hidden" name="XDEBUG_PROFILE" value="1" /> Enter a number: <input type="text" name="n"></input> </form> <hr /> <?php if ( ! empty( $_REQUEST['n'] ) ) { $n = $_REQUEST['n'] % 10; $suffix = array( 1 => "st", 2 => "nd", 3 => "rd" ); if ( $_REQUEST['n'] < 4 || $_REQUEST['n'] > 20 ) { $suffix = $suffix[$n]; } else { $suffix = 'th'; } echo '<p>The ' . $_REQUEST['n'] . $suffix .' Fibonacci number is '; echo fib( $_REQUEST['n'] ) . '</p>'; } ?> </body> </html> |
브라우저를 통해 http://localhost/fibonacci.php (또는 알맞은 URL)로 가서 숫자(이를 테면, 16)를 입력한다. Fibonacci 시리즈의 16번째 엘리먼트가 나타날 것이다. (그림 2)
그림 2. Fibonacci 애플리케이션 샘플
프로파일러 아웃풋 디렉토리(php.ini)의 콘텐트를 리스팅 하면, cachegrind.out.951917687 같은 이름을 가진 파일을 볼 수 있다. 접두사 cachegrind.out.는 고정이다. 기본적으로 숫자 접미사는 fibonacci.php 파일에 대한 디렉토리 경로의 CRC32 해시이다. 따라서, 각 애플리케이션들이 고유의 디렉토리에 있다면, 각 애플리케이션에서 온 아웃풋이 파일 이름에 따라 분리된다. (아웃풋과 시간을 연결하고 싶다면,
xdebug.profiler_output_name = timestamp |
위 라인을 php.ini 에 추가한다.)
터미널 윈도우에서, KCacheGrind
를 시작하고 cachegrind.out.951917687을 연다. 그림 3과 비슷한 새로운 윈도우가 바로 열린다.
그림 3. KCacheGrind 애플리케이션
Callees 탭을 클릭하고, 소스 코드에서 하이라이트 된 부분을 더블 클릭 한 후, Grouping 리스트에서 Source File을 선택한다. 그림 4와 같은 모습의 뷰가 보인다.
그림 4. 결과 보기
여러분도 예상했듯이, 모든 프로세싱 시간(70,989 밀리초의 99.87%)이 fib()
함수의 3,193개 호출을 실행하는데 소비되었다. Fibonacci 시퀀스로 진행해 가면서 느려진 애플리케이션 속도를 빠르게 하기 위해, Fibonacci 넘버를 재 계산 해야 하는 "값비싼" 작업은 피해야 한다. 실제로 ACME Fibonacci Maker는 전산 재사용을 적용할 수 있는 좋은 기회이다.
fib()
함수의 업데이트 버전은 아래에 나와있다. 이 새로운 버전은 시간과 메모리를 교환한다. 나중에 사용하기 위해 중간 전산을 보유하기 때문이다. 그림 5는 분석 결과이다. 3,192개의 함수 호출 대신, 단 30개만 요구되고(이 중에서 절반만 결과를 계산했다.), 시간은 20밀리초로 줄어들었다.
Listing 6. 업데이트 된 fib() 함수
function fib($nth = 1) { static $fibs = array(); if ( ! empty ($fibs[$nth] ) ) { return( $fibs[$nth] ); } if ( $nth < 2 ) { $fibs[$nth] = $nth; } else { $fibs[$nth - 1] = fib( $nth - 1 ); $fibs[$nth - 2] = fib( $nth - 2 ); $fibs[$nth] = $fibs[$nth - 1] + $fibs[$nth -2]; } return( $fibs[$nth] ); } ?> |
그림 5. 더 빨라진 Fibonacci 함수
애플리케이션이 단일 실행을 통해 문제들을 드러낼 수 있지만(원래 애플리케이션에서 Fibonacci 시퀀스 중 50번째 엘리먼트를 실행하기), 일반적으로 여러 호출들에 대한 통계를 모아서 패턴을 분석한다.
기본적인 "crc32" 네이밍 스킴을 보유하고 있다면 fibonacci.php를 실행할 때마다 데이터 파일이 오버라이트 된다. 하지만, php.ini 에서 xdebug.profiler_append = 1
을 설정함으로써 그 작동을 변경하고 후속 실행들을 같은 파일에 붙일 수 있다. 변경한 후에 웹 서버를 재시작 한다.
Fibonacci Maker를 세 번 실행 하여 수집된 데이터 예제는 그림 6에 나타나 있다. 걸린 시간은 2초 미만이다. 시간의 99.97 퍼센트가 fib()
에 사용되었다. 그림 6은 Call Graph 탭을 보여주는데, 이는 GraphViz
의 dot
유틸리티에 의해 생성된다. KCacheGrind
의 상세한 사용법은 이 글에서는 설명하지 않겠다. 온라인에서 문서를 참조하기 바란다. KCacheGrind
는 여러 가지 방법으로 데이터를 쪼개기 때문에 해결하고자 하는 문제를 찾아서 보면 된다.
그림 6. 프로파일링 데이터 모으기
|
|
프 로파일링을 설명하려면 많은 코드가 필요하지만, 다음 예제에서는 예제 코드에서 만들어 낼 수 있는 정보의 양과 유형을 보여준다. Listing 7은 장난감 로켓을 조립하는 애플리케이션이다. 이 로켓은 여러 부분들로 구성되어 있고, 각 부분이 만들어 지려면 일정 시간이 필요하다. PHP에서, 하나의 클래스는 각 부분을 나타내고, 인스턴스 메소드는 각 부분의 구현 시간을 나타낸다. 장난감을 애플리케이션으로, 각 부분을 기능으로 생각하면 된다.
Listing 7. 장난감 조립 과정을 모방한 PHP 클래스
<?php define( 'BOOSTER', 5 ); define( 'CAPSULE', 2 ); define( 'MINUTE', 60 ); define( 'STAGE', 3 ); define( 'PRODUCTION', 1000 ); class Part { function Part() { $this->build( MINUTE ); } function build( $delay = 0 ) { if ( $delay <= 0 ) return; while ( $delay-- > 0 ) { } } } class Capsule extends Part { function Capsule() { parent::Part(); $this->build( CAPSULE * MINUTE ); } } class Booster extends Part { function Booster() { parent::Part(); $this->build( BOOSTER * MINUTE ); } } class Stage extends Part { function Stage() { parent::Part(); $this->build( STAGE * MINUTE ); } } class SpaceShip { var $booster; var $capsule; var $stages; function SpaceShip( $numberStages = 3 ) { $this->booster = new Booster(); $this->capsule = new Capsule(); $this->stages = array(); while ( $numberStages-- >= 0 ) { $stages[$numberStages] = new Stage(); } } } $toys = array(); $count = PRODUCTION; while ( $count-- >= 0 ) { $toys[] = new SpaceShip( 2 ); } ?> <html> <head> <title> Toy Factory Output </title> </head> <body> <h1>Toy Production</h1> <p>Built <? echo PRODUCTION . ' toys' ?></p> </body> </html> |
이 코드를 실행하면 새로운 데이터 파일이 만들어 진다. 데이터를 KCacheGrind
로 로딩한다. Source와 Call Graph 탭으로 전환하면 뷰는 그림 7과 같이 된다.
그림 7. 우주선 애플리케이션의 프로파일
Flat Profile(왼쪽)은 호출되는 모든 함수들(메소드)를 보여준다. 가장 왼쪽의 칼럼은 대략적인 누적 합계를 보여주고, 두 번째 칼럼은 각 메소드에 대한 개별적인 측정을 보여주며, 세 번째 칼럼은 메소드가 호출되는데 걸리는 시간을 나타낸다. 색깔이 칠해진 사각형은 호출 그래프를 반영하며 타이밍과 이벤트 순서를 쉽게 연관시킬 수 있다.
분명한 것은, 단계들을 구현하는 시간이 가장 비싸다는 것이다. 각 부분들(Part
의 컨스트럭터로 나타남)을 구현하는데 필요한 오버헤드는 그 다음이다. 또한 PHP의 define()
함수에도 약간의 비용이 필요하다.
마지막으로, 메모리가 사용되었던 방식도 볼 수 있다. 상단 드롭다운 메뉴에서 Memory와 Class를 선택하고, 위와 아래에 있는 Types와 Caller Map으로 전환한다. 스크린은 그림 8처럼 보인다.
그림 8. 우주선 애플리케이션에서의 메모리 사용
|
|
다 른 많은 PHP 확장과 마찬가지로 Xdebug는 즉각 구현되고, 빠르게 설치되며, 쉽게 설정된다. 이 모든 것이 10분 안에 끝난다. Apache가 이미 최적화 되고 애플리케이션이 캐싱 되고 있지만, 성능이 나아지지 않는다면 코드가 어떻게 실행되고 있는지를 봐야 한다. 알고리즘은 효율적인가? 코드가 너무 복잡한가? PHP가 이미 제공하고 있는 함수를 재구현 하고 있는 것은 아닌가?
병목 현상을 찾아낼 수 없다면, 느려짐의 원인을 찾아 픽스할 때이다. 바로 프로파일이 필요하다. 여러분의 귀중한 전산 사이클이 어떻게 소비되는지를 알게 된다면 놀라게 될 것이다.
잊지 말아야 할 것은 Xdebug가 실행 서버에서 실행된다면 오버헤드를 가중시키므로 실행 서버에서는 Xdebug를 실행시키지 말아야 한다.
교육
- 이 시리즈의 Part 1과 Part 3 보기.
- PHP.net은 PHP 개발자들을 위한 사이트이다.
- "Recommended PHP reading list"를 확인해보라.
- developerWorks의 PHP 콘텐트 탐색하기.
- 한국 developerWorks의 PHP 콘텐트 탐색하기.
- IBM developerWorks의 PHP 프로젝트 관련자료를 이용해 PHP 스킬을 넓혀보라.
- 소프트웨어 개발자와의 흥미로운 인터뷰와 토론을 듣기 위해 developerWorks podcasts를 확인해보라.
- developerWorks의 기술 이벤트와 웹캐스트.
- PHP용 데이터베이스를 사용하는가? 그렇다면 IBM의 Zend Core를 참조해보라.
- IBM 오픈 소스 개발자라면 다가오는 컨퍼런스, 트래이드쇼, 웹캐스트, 기타 이벤트를 확인해보라.
- 오픈 소스 기술로 개발하는 것과 IBM 제품을 사용하는 것을 돕기 위해 설명 정보, 도구, 프로젝트 갱신 이력을 제공하는 한국 developerWorks 오픈 소스 존에 방문하라.
제품 및 기술 얻기
- Xdebug 소프트웨어 다운로드.
- DVD나 다운로드할 수 있는 IBM 시험판 소프트웨어로 다음 오픈 소스 개발 프로젝트를 개선해보라.
출처 : http://www.ibm.com/developerworks/kr/library/os-php-fastapps2/index.html
'Server&OS > Linux' 카테고리의 다른 글
PHP 애플리케이션을 가장 빠르게, Part 3: Memcache 데몬을 사용하여 메모리에 데이터 저장(cache)하기 (0) | 2010.04.24 |
---|---|
Qmail 설치 가이드 (0) | 2010.04.24 |
postgres 을 cron으로 자동백업 (0) | 2010.04.24 |
pokebi (0) | 2010.04.24 |
PHP 애플리케이션을 가장 빠르게, Part 1: opcode 캐시 소프트웨어로 성능과 처리량 높이기 (0) | 2010.04.24 |